今天遇到了個(gè)面試,其中有的問題我當(dāng)時(shí)還真不能確定,遂發(fā)出來,大家分享。
先大致講一下流程,一面還挺順利,游刃有余;二面就有些緊張了,是個(gè)額頭頭發(fā)不多但是顯得很精干的男士(下文簡稱為A)。
只摘錄其中的部分我很“為難”的地方:
A:string是值類型是引用類型?
ME:(我心想string是class,肯定是)引用類型
A:那我有個(gè)方法,參數(shù)為string,我在里面改變他的值,原來的會變嗎?
ME:(這個(gè)我當(dāng)時(shí)很猶豫,雖說string平時(shí)用,但是還真考慮過這個(gè)。我要是說會不會變吧,豈不是自打嘴巴?String是引用類型,怎么還值專遞呢?)
       當(dāng)時(shí)我就記得園子里有句話:String是引用類型,但是用起來像值類型。我就說的是不變。
下面上一段代碼分析一下:
static void Foo(string  s)         {             s = "bbb";         }
  string s = "aaa";             Foo(s); Console.WriteLine(s);  | 
這個(gè)確實(shí)是不會變的,調(diào)用完之后還是“aaa”,這是為什么呢?
1 string s = "aaa";  2 00000051 8B 05 88 20 C0 02 mov         eax,dword ptr ds:[02C02088h]   3 00000057 89 45 B8         mov         dword ptr [ebp-48h],eax   4     92:             Foo(s);  5 0000005a 8B 4D B8         mov         ecx,dword ptr [ebp-48h]   6 0000005d E8 A6 AF D4 FF   call        FFD4B008   7 00000062 90               nop                8     93:             Console.WriteLine(s);  9 00000063 8B 4D B8         mov         ecx,dword ptr [ebp-48h]  10 00000066 E8 95 24 3F 67   call        673F2500  11  12  13  14  15  16  static void Foo(string  s) 17     82:         { 18 00000000 55               push        ebp   19 00000001 8B EC            mov         ebp,esp  20 00000003 57               push        edi   21 00000004 56               push        esi   22 00000005 53               push        ebx   23 00000006 83 EC 30         sub         esp,30h  24 00000009 33 C0            xor         eax,eax  25 0000000b 89 45 F0         mov         dword ptr [ebp-10h],eax  26 0000000e 33 C0            xor         eax,eax  27 00000010 89 45 E4         mov         dword ptr [ebp-1Ch],eax  28 00000013 89 4D C4         mov         dword ptr [ebp-3Ch],ecx  29 00000016 83 3D E0 8C 7B 00 00 cmp         dword ptr ds:[007B8CE0h],0  30 0000001d 74 05            je          00000024  31 0000001f E8 1D 91 57 68   call        68579141  32 00000024 90               nop               33     83:             s = "bbb"; 34 00000025 8B 05 90 20 C0 02 mov         eax,dword ptr ds:[02C02090h]  35 0000002b 89 45 C4         mov         dword ptr [ebp-3Ch],eax  36     84:         } 37 0000002e 90               nop               38 0000002f 8D 65 F4         lea         esp,[ebp-0Ch]  39 00000032 5B               pop         ebx   40 00000033 5E               pop         esi   41 00000034 5F               pop         edi   42 00000035 5D               pop         ebp   43 00000036 C3               ret                | 
可以看到第2行將字符串的地址寫入到 eax,然后寫到堆棧的【ebp-48h】處;
調(diào)用Foo方法前,放到ecx中。
在方法Foo中,可以看到又經(jīng)ecx放到了【ebp-3Ch】處;
在執(zhí)行s=“bbb”的時(shí)候,同樣將新字符串的地址放到了【ebp-3Ch】處,但是原來的字符串并為更改,只是更改了臨時(shí)變量s的引用。
所以在調(diào)用完方法Foo之后,原來的字符串還是“aaa”,沒有改變。
所以這個(gè)時(shí)候我回答不變是對的,但是我不知道為什么string的傳遞是類似于值傳遞的,有點(diǎn)運(yùn)氣了。
接下來,他又問
A:那如果我有個(gè)類,里面有string成員,我同樣改變他的值,外面的會變嗎?這個(gè)時(shí)候我回答的是可以改變。
是不是這樣呢?同樣,上代碼:
1 class C1  2     {  3         public string s1="aaa";  4     }  5   6  static void Foo(C1   c1)  7         {  8             c1.s1  = "bbb";  9         } 10  11  C1 c1 = new C1(); 12             Foo(c1); 13             Console.WriteLine(c1.s1 );  | 
 
1  Foo(c1);  2 0000006c 8B 4D B8         mov         ecx,dword ptr [ebp-48h]   3 0000006f E8 94 AF 7F FF   call        FF7FB008   4 00000074 90               nop                5     93:             Console.WriteLine(c1.s1 );  6 00000075 8B 45 B8         mov         eax,dword ptr [ebp-48h]   7 00000078 8B 48 04         mov         ecx,dword ptr [eax+4]   8 0000007b E8 80 24 52 67   call        67522500   9  10  11  12  static void Foo(C1   c1) 13     82:         { 14 00000000 55               push        ebp   15 00000001 8B EC            mov         ebp,esp  16 00000003 57               push        edi   17 00000004 56               push        esi   18 00000005 53               push        ebx   19 00000006 83 EC 30         sub         esp,30h  20 00000009 33 C0            xor         eax,eax  21 0000000b 89 45 F0         mov         dword ptr [ebp-10h],eax  22 0000000e 33 C0            xor         eax,eax  23 00000010 89 45 E4         mov         dword ptr [ebp-1Ch],eax  24 00000013 89 4D C4         mov         dword ptr [ebp-3Ch],ecx  25 00000016 83 3D E0 8C 13 00 00 cmp         dword ptr ds:[00138CE0h],0  26 0000001d 74 05            je          00000024  27 0000001f E8 AD 90 6A 68   call        686A90D1  28 00000024 90               nop               29     83:             c1.s1  = "bbb"; 30 00000025 8B 05 90 20 D7 02 mov         eax,dword ptr ds:[02D72090h]  31 0000002b 8B 4D C4         mov         ecx,dword ptr [ebp-3Ch]  32 0000002e 8D 51 04         lea         edx,[ecx+4]  33 00000031 E8 9A 16 45 68   call        684516D0  34     84:         } 35 00000036 90               nop               36 00000037 8D 65 F4         lea         esp,[ebp-0Ch]  37 0000003a 5B               pop         ebx   38 0000003b 5E               pop         esi   39 0000003c 5F               pop         edi   40 0000003d 5D               pop         ebp   41 0000003e C3               ret                | 
在執(zhí)行30行的時(shí)候eax是01DBC268,其內(nèi)存的內(nèi)容拷貝出來是:
54 0b a0 67 04 00 00 00 03 00 00 00 62 00 62 00 62 00 00 00 00  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | 
可以看出這是一個(gè)string的實(shí)例,前面的67a00b54是MT的地址,后面的00000004是字符串的實(shí)際長度,00000003是字符串有效內(nèi)容的長度,
后面的3個(gè)0062是連著三個(gè)字符‘b’,看來確實(shí)是字符串“bbb”。再后面00的就不管了。
接著依次執(zhí)行31和32行,則ecx是01D9EEC8,edx是01D9EECC;據(jù)猜測ecx應(yīng)該是c1的地址,把內(nèi)存考出來看一下:
d0 99 41 00 94 ee d9 01 00 00 00 00 24 43 9d 67 0a 00 00 00 70 07 a0 67  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  | 
而此時(shí)edx就應(yīng)該是s1的地址,可以看出edx就比ecx相差4,所以01d9ee94就應(yīng)該是字符串“aaa”的地址,同樣考出來看看:
54 0b a0 67 04 00 00 00 03 00 00 00 61 00 61 00 61 00 00 00 00 00 00
  | 
可以看出,“aaa”和“bbb”的頭幾個(gè)部分完全是一樣的,就是后面的一個(gè)是61,一個(gè)是62.
那么問題很簡單了,知道把c1里的字符串地址從01d9ee94換成01DBC268就算OK了。事實(shí)上33行就是做這個(gè)事情的。
看一下執(zhí)行完33行后的c1的內(nèi)容:
d0 99 41 00 68 c2 db 01 00 00 00 00 24 43 9d 67 0a 00 00 00 70 07 a0 67  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | 
可以看出,確實(shí)是換了。
所以到這里,問題解決了。
#p#
接著這老大又問
A:有沒有其他方法可以改變字符串?
ME:加ref或out關(guān)鍵字可以,或者用指針。
我們看一下加ref(或加out,其實(shí)是一樣的)的為什么可以改變,更詳細(xì)的看一下。
static void Foo(ref string  s)         {             s= "bbb";         }
  string s = "aaa";             Foo(ref s);             Console.WriteLine(s );  | 
繼續(xù)匯編:
1  string s = "aaa";  2 0000004c 8B 05 88 20 ED 02 mov         eax,dword ptr ds:[02ED2088h]   3 00000052 89 45 B8         mov         dword ptr [ebp-48h],eax   4     92:             Foo(ref s);  5 00000055 8D 4D B8         lea         ecx,[ebp-48h]   6 00000058 E8 AB AF D0 FF   call        FFD0B008   7 0000005d 90               nop                8     93:             Console.WriteLine(s );  9 0000005e 8B 4D B8         mov         ecx,dword ptr [ebp-48h]  10 00000061 E8 9A 24 49 67   call        67492500  11  12  13  14   static void Foo(ref string  s) 15     82:         { 16 00000000 55               push        ebp   17 00000001 8B EC            mov         ebp,esp  18 00000003 57               push        edi   19 00000004 56               push        esi   20 00000005 53               push        ebx   21 00000006 83 EC 30         sub         esp,30h  22 00000009 33 C0            xor         eax,eax  23 0000000b 89 45 F0         mov         dword ptr [ebp-10h],eax  24 0000000e 33 C0            xor         eax,eax  25 00000010 89 45 E4         mov         dword ptr [ebp-1Ch],eax  26 00000013 89 4D C4         mov         dword ptr [ebp-3Ch],ecx  27 00000016 83 3D E0 8C 6D 00 00 cmp         dword ptr ds:[006D8CE0h],0  28 0000001d 74 05            je          00000024  29 0000001f E8 1D 91 61 68   call        68619141  30 00000024 90               nop               31     83:             s= "bbb"; 32 00000025 8B 05 90 20 ED 02 mov         eax,dword ptr ds:[02ED2090h]  33 0000002b 8B 4D C4         mov         ecx,dword ptr [ebp-3Ch]  34 0000002e 8D 11            lea         edx,[ecx]  35 00000030 E8 A3 0E 3C 68   call        683C0ED8  36     84:         } 37 00000035 90               nop               38 00000036 8D 65 F4         lea         esp,[ebp-0Ch]  39 00000039 5B               pop         ebx   40 0000003a 5E               pop         esi   41 0000003b 5F               pop         edi   42 0000003c 5D               pop         ebp   43 0000003d C3               ret                | 
同樣,關(guān)注代碼的32~34行:
eax:01DEC25C,內(nèi)容:
54 0b a0 67 04 00 00 00 03 00 00 00 62 00 62 00 62 00 00 00 00 00  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | 
確實(shí)是字符串“bbb”
ecx和edx都是:05C7E778,內(nèi)容:0x01dcee94,這個(gè)是字符串“aaa”的地址。
執(zhí)行完35行之后,地址05C7E778的內(nèi)容變成了01DEC25C,在之后第9行代碼確實(shí)地址變成了01DEC25C,則可以推斷05C7E778是上個(gè)堆棧
s引用的位置,則35行的代碼則是將新“bbb”的地址寫到原來的s引用處。
A繼續(xù)問:ref和out有什么區(qū)別?
ME:我說兩者沒什么區(qū)別,就是out不要求變量初始化。
A:那要是初始化了呢,改變了之后是什么值?
ME:(這個(gè)我還真被問住了。不知道可以,但是不能亂說啊。)基于對out這個(gè)關(guān)鍵字的理解,我認(rèn)為應(yīng)該返回改變后的值。
如果將原來的ref改為out,匯編代碼完全相似,區(qū)別就是變量是否初始化問題,如果不初始化,其實(shí)變量在棧中也是有位置的,只不過地址內(nèi)容為0.
如果初始化,則和ref完全一樣。代碼我就不貼了,大家可以自己調(diào)式看一看。
問題:為什么默認(rèn)的字符串作為參數(shù)傳遞是類似的值傳遞呢?請大家告訴我。
靠,弄了半天,才記得所有傳遞默認(rèn)都是值傳遞,這才是問題的根源。老了,腦袋記不清了,以前看C語言的時(shí)候還特別注意了這點(diǎn),結(jié)果還是忘記了。
問題的答案請看我最下面的留言。
在這里有些誤導(dǎo)大家了,給大家致歉。
【編輯推薦】
- 求職者看面試官:和不懂技術(shù)的人談技術(shù)
 
- 思科認(rèn)證CCIE考試介紹:費(fèi)用及實(shí)驗(yàn)面試等
 
- 面試官:我如何招到聰明又能做事的人