A. 什麼時候在block中不需要使用weakSelf
Objective C 的 Block 是一個很實用的語法,特別是與GCD結合使用,可以很方便地實現並發、非同步任務。但是,如果使用不當,Block 也會引起一些循環引用問題(retain cycle)——
Block 會 retain 『self』,而 『self『 又 retain 了 Block。因為在 ObjC
中,直接調用一個實例變數,會被編譯器處理成 『self->theVar』,』self』 是一個 strong 類型的變數,引用計數會加
1,於是,self
retains queue, queue retains block,block retains self。
解決 retain circle
Apple 官方的建議是,傳進 Block 之前,把 『self』 轉換成 weak automatic 的變數,這樣在 Block
中就不會出現對 self 的強引用。如果在 Block 執行完成之前,self 被釋放了,weakSelf 也會變為 nil。
示例代碼:
1 2 3 4
__weak
__typeof__(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
0), ^{ [weakSelf doSomething]; });
clang 的文檔表示,在 doSomething 內,weakSelf 不會被釋放。但,下面的情況除外:
1 2 3 4 5
__weak
__typeof__(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
0), ^{ [weakSelf doSomething]; [weakSelf doOtherThing]; });
在 doSomething 中,weakSelf 不會變成 nil,不過在 doSomething 執行完成,調用第二個方法 doOtherThing 的時候,weakSelf 有可能被釋放,於是,strongSelf 就派上用場了:
1 2 3 4 5 6
__weak
__typeof__(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
0), ^{ __strong __typeof(self) strongSelf = weakSelf; [strongSelf
doSomething]; [strongSelf doOtherThing]; });
__strong 確保在 Block 內,strongSelf 不會被釋放。
總結
在 Block 內如果需要訪問 self 的方法、變數,建議使用 weakSelf。
如果在 Block 內需要多次 訪問 self,則需要使用 strongSelf。
B. STM32的hal庫中__weak函數前綴的作用
在使用STM32的hal庫的時候,我們常常可以看到很多庫自帶的函數有很多是使用__weak修飾的,比如:
weak 顧名思義是「弱」的意思,所以如果函數名稱前面加上__weak 修飾符,我們一般稱這個函數為 「弱函數」 。
加上了__weak 修飾符的函數,用戶可以在用戶文件中重新定義一個同名函數,最終編譯器編譯的時候,會選擇用戶定義的函數,如果用戶沒有重新定義這個函數,那麼編譯器就會執行__weak 聲明的函數,並且編譯器不會報錯。
__weak是一個宏,和__packed是同一種東西都是gcc的擴展屬性:
#define __packed __attribute__((packed))
#define __weak __attribute__((weak))
如果這個關鍵字用在函數定義上面,一般情況下和一般函數沒有兩樣。但是當有一個同名函數但是不帶__weak被定義時,所有對這個函數的調用都是指向後者(不帶__weak那個), 如果有兩個一樣的函數都用了__weak,那麼真正調用那個,就要看連接器了。
例子:
我們打開一個工程
可以看出,HAL_CAN_TxCpltCallback 函數前面有加修飾符__weak。同時,並且 CAN_Transmit_IT函數中調用了函數 HAL_CAN_TxCpltCallback。
如果我們沒有在工程中其他地方重新定義 HAL_CAN_TxCpltCallback ()函數,那麼 CAN_Transmit_IT初始化函數執行的時候,會默認執行 stm32f0xx_hal_can.c 文件中定義的 HAL_CAN_TxCpltCallback 函數,而這個函數沒有任何控制邏輯。
如果用戶在工程中重新定義函數 HAL_CAN_TxCpltCallback ,那麼調用 CAN_Transmit_IT之後,會執行用戶自己定義的HAL_CAN_TxCpltCallback 函數而不會執行 stm32f0xx_hal_can.c 默認定義的函數。也就是說,表面上我們看到函數 HAL_CAN_TxCpltCallback 被定義了兩次,但是因為有一次定義是弱函數,使用了__weak修飾符,所以編譯器不會報錯。
願你出走半生,歸來仍是少年…
C. 編寫一個程序,讀取5個整數並確定和列印其中的最大值,以下是我寫的代碼,錯了,誰能幫我改改
系統看成是各種對象的集合,這更接近人的思維。
2)軟體需求的變動往往是功能的變動,而功能的執行者--對象一般不會有太大的變化。這使得按照對象設計出來的系統結構比較穩定。
3)對象包括屬性和方法,對象把屬性和方法的具體實現方式一起封裝起來,這使得方法與之相關的屬性不再分離,提高每個子系統的相對獨立性,從而提高了軟體的可維護性。
4)支持封裝、繼承、多態和抽象,提高了軟體的可重用性、可維護性和可擴展性。
2.把一個類放在包里有什麼作用?(包的作用)
1)能夠區分名字相同的類。
2)有助於實施訪問許可權控制。
3)有助於劃分和組織java應用中的各個類。
3.說出一些常用的類,包,介面,請各舉出5個。
Runable,ActionListener,Conllection,Map,Set,List介面
1)java.lang包----包括線程類(Thread)、異常類(Exception)、系統類(System)、整數類(Integer)和字元串類(String) 等, 這些類是java程序中經常用到的。
2)java.awt包----抽象窗口工具箱包,awt是(Abstract Window Toolkit) 的縮寫。這個包中包含了用於構建GUI界面的類及繪圖類。
3)java.io包----輸入/輸出包,包含各種輸入流類和輸出流類,如文件輸入流類(FileInputStream類)及文件輸出流類(FileOutputStream)等。
4)java.util包----提供一些實用類,如日期類(Data)和集合類(Collection)等。
5)java.net包----支持TCP/IP網路協議,包括Socket類及和URL相關的類,這些類都用於網路編程。
除了上面提到的基本包,JDK中還有很多其他包,比如用於資料庫編程的java.sql包,用於編寫網路程序的java.rmi包(rmi是「Remote Method Invocation」的縮寫)。另外,javax.*包是對基本包的擴展,包括用於編寫GUI的javax.Swing包,以及用於編寫聲音程序的javax.sound包等。
4. 描述一下你最常用的編程風格。
1)注意編碼規則,符合編碼要求。
2)變數,類等起名要有意義。
3)經常格式化代碼,注意格式。
4)代碼中加入測試方法或測試類,盡量提早發現錯誤。
5)代碼中要加入注釋,為別人和自己將來理解代碼帶來方便。
5. 說一說標識符的命名規則,以及java的編程規范。
Java標識符的命名規則:
1) 標識符由字母、數字、下劃線「_」、美元符號「$」或者人民幣符號「¥」組成,並且首字母不能是數字。
2) 不能把關鍵字和保留字作為標識符。
3) 標識符沒有長度限制。
4) 標識符對大小寫敏感。
Java編程規范:
1)類名和介面名:首字母大寫,其餘字母小寫。如SamDoc
2)方法名和變數名:首字母小寫,其餘的字母大寫。
如bothEyesOfDoll。
3)包名:字母全部小寫。如,com.abc.dollapp。
4)常量名:採用大寫形式,單詞之間以下劃線「_」隔開。
如DEFAULT_COLOR_DOL。
…………………………
31、介紹JAVA中的Collection FrameWork(包括如何寫自己的數據結構)?
答:Collection FrameWork如下:
Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set
Map
├Hashtable
├HashMap
└WeakHashMap
Collection是最基本的集合介面,一個Collection代表一組Object,即Collection的元素(Elements)
Map提供key到value的映射
32、抽象類與介面?
答:抽象類與介面都用於抽象,但是抽象類(JAVA中)可以有自己的部分實現,而介面則完全是一個標識(同時有多重繼承的功能)。
JAVA類實現序例化的方法是實現java.io.Serializable介面
Collection框架中實現比較要實現Comparable 介面和 Comparator 介面
33、STRING與STRINGBUFFER的區別。
答:STRING的長度是不可變的,STRINGBUFFER的長度是可變的。如果你對字元串中的內容經常進行操作,特別是內容要修改時,那麼使用StringBuffer,如果最後需要String,那麼使用StringBuffer的toString()方法
34、談談final, finally, finalize的區別
答:final?修飾符(關鍵字)如果一個類被聲明為final,意味著它不能再派生出新的子類,不能作為父類被繼承。因此一個類不能既被聲明為 abstract的,又被聲明為final的。將變數或方法聲明為final,可以保證它們在使用中不被改變。被聲明為final的變數必須在聲明時給定初值,而在以後的引用中只能讀取,不可修改。被聲明為final的方法也同樣只能使用,不能重載
finally?再異常處理時提供 finally 塊來執行任何清除操作。如果拋出一個異常,那麼相匹配的 catch 子句就會執行,然後控制就會進入 finally 塊(如果有的話)
finalize?方法名。Java 技術允許使用 finalize() 方法在垃圾收集器將對象從內存中清除出去之前做必要的清理工作。這個方法是由垃圾收集器在確定這個對象沒有被引用時對這個對象調用的。它是在 Object 類中定義的,因此所有的類都繼承了它。子類覆蓋 finalize() 方法以整理系統資源或者執行其他清理工作。finalize() 方法是在垃圾收集器刪除對象之前對這個對象調用的
35、面向對象的特徵有哪些方面
答:主要有以下四方面:
1.抽象:
抽象就是忽略一個主題中與當前目標無關的那些方面,以便更充分地注意與當前目標有關的方面。抽象並不打算了解全部問題,而只是選擇其中的一部分,暫時不用部分細節。抽象包括兩個方面,一是過程抽象,二是數據抽象。
2.繼承:
繼承是一種聯結類的層次模型,並且允許和鼓勵類的重用,它提供了一種明確表述共性的方法。對象的一個新類可以從現有的類中派生,這個過程稱為類繼承。新類繼承了原始類的特性,新類稱為原始類的派生類(子類),而原始類稱為新類的基類(父類)。派生類可以從它的基類那裡繼承方法和實例變數,並且類可以修改或增加新的方法使之更適合特殊的需要。
3.封裝:
封裝是把過程和數據包圍起來,對數據的訪問只能通過已定義的界面。面向對象計算始於這個基本概念,即現實世界可以被描繪成一系列完全自治、封裝的對象,這些對象通過一個受保護的介面訪問其他對象。
4. 多態性:
多態性是指允許不同類的對象對同一消息作出響應。多態性包括參數化多態性和包含多態性。多態性語言具有靈活、抽象、行為共享、代碼共享的優勢,很好的解決了應用程序函數同名問題。
36、String是最基本的數據類型嗎
答:基本數據類型包括byte、int、char、long、float、double、boolean和short。
java.lang.String類是final類型的,因此不可以繼承這個類、不能修改這個類。為了提高效率節省空間,我們應該用StringBuffer類
37、int 和 Integer 有什麼區別
答:Java 提供兩種不同的類型:引用類型和原始類型(或內置類型)。Int是java的原始數據類型,Integer是java為int提供的封裝類。Java為每個原始類型提供了封裝類。原始類型封裝類booleanBoolean,charCharacter,byteByte,shortShort,intInteger,
longLong,floatFloat,doubleDouble
引用類型和原始類型的行為完全不同,並且它們具有不同的語義。引用類型和原始類型具有不同的特徵和用法,它們包括:大小和速度問題,這種類型以哪種類型的數據結構存儲,當引用類型和原始類型用作某個類的實例數據時所指定的預設值。對象引用實例變數的預設值為 null,而原始類型實例變數的預設值與它們的類型有關
38、運行時異常與一般異常有何異同
答:異常表示程序運行過程中可能出現的非正常狀態,運行時異常表示虛擬機的通常操作中可能遇到的異常,是一種常見運行錯誤。java編譯器要求方法必須聲明拋出可能發生的非運行時異常,但是並不要求必須聲明拋出未被捕獲的運行時異常。
39、說出ArrayList,Vector, LinkedList的存儲性能和特性
答:ArrayList和Vector都是使用數組方式存儲數據,此數組元素數大於實際存儲的數據以便增加和插入元素,它們都允許直接按序號索引元素,但是插入元素要涉及數組元素移動等內存操作,所以索引數據快而插入數據慢,
Vector由於使用了synchronized方法(線程安全),通常性能上較ArrayList差,而LinkedList使用雙向鏈表實現存儲,按序號索引數據需要進行前向或後向遍歷,但是插入數據時只需要記錄本項的前後項即可,所以插入速度較快。
40、HashMap和Hashtable的區別
答:HashMap是Hashtable的輕量級實現(非線程安全的實現),他們都完成了Map介面,主要區別在於HashMap允許空(null)鍵值(key),由於非線程安全,效率上可能高於Hashtable。
HashMap允許將null作為一個entry的key或者value,而Hashtable不允許。
HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因為contains方法容易讓人引起誤解。
Hashtable繼承自Dictionary類,而HashMap是Java1.2引進的Map interface的一個實現。
最大的不同是,Hashtable的方法是Synchronize的,而HashMap不是,在多個線程訪問Hashtable時,不需要自己為它的方法實現同步,而HashMap 就必須為之提供外同步。
Hashtable和HashMap採用的hash/rehash演算法都大概一樣,所以性能不會有很大的差異。
41、heap和stack有什麼區別
答:棧是一種線形集合,其添加和刪除元素的操作應在同一段完成。棧按照後進先出的方式進行處理。堆是棧的一個組成元素
42、Java的介面和C++的虛類的相同和不同處
答:由於Java不支持多繼承,而有可能某個類或對象要使用分別在幾個類或對象裡面的方法或屬性,現有的單繼承機制就不能滿足要求。與繼承相比,介面有更高的靈活性,因為介面中沒有任何實現代碼。當一個類實現了介面以後,該類要實現介面裡面所有的方法和屬性,並且介面裡面的屬性在默認狀態下面都是public static,所有方法默認情況下是public.一個類可以實現多個介面。
43、Java中的異常處理機制的簡單原理和應用
答:當JAVA程序違反了JAVA的語義規則時,JAVA虛擬機就會將發生的錯誤表示為一個異常。違反語義規則包括2種情況。一種是JAVA類庫內置的語義檢查。例如數組下標越界,會引發IndexOutOfBoundsException;訪問null的對象時會引發NullPointerException。另一種情況就是JAVA允許程序員擴展這種語義檢查,程序員可以創建自己的異常,並自由選擇在何時用throw關鍵字引發異常。所有的異常都是java.lang.Thowable的子類。
43、垃圾回收的優點和原理。並考慮2種回收機制
答:Java語言中一個顯著的特點就是引入了垃圾回收機制,使c++程序員最頭疼的內存管理的問題迎刃而解,它使得Java程序員在編寫程序的時候不再需要考慮內存管理。由於有個垃圾回收機制,Java中的對象不再有"作用域"的概念,只有對象的引用才有"作用域"。垃圾回收可以有效的防止內存泄露,有效的使用可以使用的內存。垃圾回收器通常是作為一個單獨的低級別的線程運行,不可預知的情況下對內存堆中已經死亡的或者長時間沒有使用的對象進行清楚和回收,程序員不能實時的調用垃圾回收器對某個對象或所有對象進行垃圾回收。回收機制有分代復制垃圾回收和標記垃圾回收,增量垃圾回收。
44、你所知道的集合類都有哪些?主要方法?
答:最常用的集合類是 List 和 Map。 List 的具體實現包括 ArrayList 和 Vector,它們是可變大小的列表,比較適合構建、存儲和操作任何類型對象的元素列表。 List 適用於按數值索引訪問元素的情形。
Map 提供了一個更通用的元素存儲方法。 Map 集合類用於存儲元素對(稱作"鍵"和"值"),其中每個鍵映射到一個值。
45、描述一下JVM載入class文件的原理機制?
答:JVM中類的裝載是由ClassLoader和它的子類來實現的,Java ClassLoader 是一個重要的Java運行時系統組件。它負責在運行時查找和裝入類文件的類。
46、排序都有哪幾種方法?請列舉
答: 排序的方法有:插入排序(直接插入排序、希爾排序),交換排序(冒泡排序、快速排序),選擇排序(直接選擇排序、堆排序),歸並排序,分配排序(箱排序、基數排序)
快速排序的偽代碼。
/ /使用快速排序方法對a[ 0 :n- 1 ]排序
從a[ 0 :n- 1 ]中選擇一個元素作為middle,該元素為支點
把餘下的元素分割為兩段left 和right,使得left中的元素都小於等於支點,而right 中的元素都大於等於支點
遞歸地使用快速排序方法對left 進行排序
遞歸地使用快速排序方法對right 進行排序
所得結果為left + middle + right
47、JAVA語言如何進行異常處理,關鍵字:throws,throw,try,catch,finally分別代表什麼意義?在try塊中可以拋出異常嗎?="3">答:Java通過面向對象的方法進行異常處理,把各種不同的異常進行分類,並提供了良好的介面。在Java中,每個異常都是一個對象,它是Throwable類或其它子類的實例。當一個方法出現異常後便拋出一個異常對象,該對象中包含有異常信息,調用這個對象的方法可以捕獲到這個異常並進行處理。Java的異常處理是通過5個關鍵詞來實現的:try、catch、throw、throws和finally。一般情況下是用try來執行一段程序,如果出現異常,系統會拋出(throws)一個異常,這時候你可以通過它的類型來捕捉(catch)它,或最後(finally)由預設處理器來處理。
用try來指定一塊預防所有"異常"的程序。緊跟在try程序後面,應包含一個catch子句來指定你想要捕捉的"異常"的類型。
throw語句用來明確地拋出一個"異常"。
throws用來標明一個成員函數可能拋出的各種"異常"。
Finally為確保一段代碼不管發生什麼"異常"都被執行一段代碼。
可以在一個成員函數調用的外面寫一個try語句,在這個成員函數內部寫另一個try語句保護其他代碼。每當遇到一個try語句,"異常"的框架就放到堆棧上面,直到所有的try語句都完成。如果下一級的try語句沒有對某種"異常"進行處理,堆棧就會展開,直到遇到有處理這種"異常"的try語句。
48、一個".java"源文件中是否可以包括多個類(不是內部類)?有什麼限制?
答:可以。必須只有一個類名與文件名相同。
49、java中有幾種類型的流?JDK為每種類型的流提供了一些抽象類以供繼承,請說出他們分別是哪些類?
答:位元組流,字元流。位元組流繼承於InputStream OutputStream,字元流繼承於InputStreamReader OutputStreamWriter。在java.io包中還有許多其他的流,主要是為了提高性能和使用方便。
50、java中會存在內存泄漏嗎,請簡單描述。
答:會。自己實現堆載的數據結構時有可能會出現內存泄露,可參看effective java.
D. 如何使用parentViewController-CSDN論壇
向後傳遞值,實現的方法有多種
1,可以用通知:當L2返回時發送通知 post notification,L1(訂閱者)接收通知傳遞過來的參數,並更新content的內容。
2,delegate代理或閉包。
定義一個代理協議
Objective C code?
1
2
3
@objc protocol L2ControllerDelegate{
func returnAndUpdateContent(vc:L2Controller, content:String)
}
在L2Controller類中定義delegate的屬性
weak var delegate : L2ControllerDelegate?
點擊返回時回調
Objective C code?
1
2
3
@IBAction backButtonPressed(sender : AnyObject!) {
self.delegate?.returnAndUpdateContent(self, content:@"hello")
}
E. Swift:weak與unowned的奧秘
以上是 Swift 官方教程中,對 Swift 內存管理的解釋。通常情況的部分很好理解,Swift 中 ARC 負責絕大部分的內存管理,ARC部分可以參考我的另一篇博客: iOS內存管理初探 – 引用計數、AutoRelease與ARC ;但少數情況下,我們需要向ARC提供對象之間的關系來使其正確管理內存。那麼少數情況是什麼意思呢?這得從Swift中的循環強引用講起。
如圖所示的情況中,john指向的對象強引用了unit4A指向的對象,而unit4A指向的對象又強引用了john指向的對象。
paragraph實例有一個成員asHTML強引用了一個閉包,而這個閉包中又捕獲了self,意味著對self的強引用。
在ARC下,引用循環的情況是編譯器無法自動解決的,這就是上文提到的少數情況。weak 和 unowned 的存才就是為了給編譯器提供更多的信息,來打破循環引用。
含義 :weak 即弱引用,當把一個實例聲明為弱引用時,此實例不會持有這個對象,即不會使對象的引用計數加1。當對象被廢棄,其所有的弱引用會被置為 nil。
適用場景 :
由於 tenant 是弱引用,當 tenant 引用的對象被銷毀(如賦值 nil),tenant 被置為空,並且釋放對 apartment的強引用,此時 apartment 所指對象就可以正常釋放了。
由於 self.delegate 指向的是外部對象,生命周期與self無關,所以可能在被捕獲後變為nil。(delegate 一般都聲明為weak以避免循環引用)
含義 :無主引用,與弱引用一樣,當把一個實例聲明為無主引用時,此實例不會持有這個對象,即不會使對象的引用計數加1。但與弱引用不同的是,當對象被廢棄,其無主引用並不會被置為 nil。
適用場景 :
雖然在這個例子中,capitalCity 與 country 的生命周期相同,理論上講將其中任何一個聲明為無主引用都可以打破引用循環,但 capitalCity 與 country 之間有從屬關系,所以傾向於將「大」的一方,即 country 聲明為無主引用。
self 與屬於 self 的成員變數 someClosure 生命周期相同,同時銷毀,所以聲明為無主引用。
Automatic Reference Counting
在Swift中,寫下 unowned 相當於 unowned(safe)。但在官方文檔中提到, Swift 還提供了另一種不安全的無主引用 unowned(unsafe) 來禁用運行時的安全檢查。運行時的安全檢查就是使 unowned(safe) 安全的原因。
unowned(safe) :當訪問 unowned(safe) 類型的無主引用時,運行時會進行安全檢查,如果對象已經廢棄,將拋出異常並終止程序。
unowned(unsafe) :unowned(unsafe) 的作用效果實際上相當於 Objective-C 屬性標示符中的 assign/unsafeunretained。訪問 unowned(unsafe) 類型的無主引用時,運行時的安全檢查被禁用,這時會有三種情況:
The Swift Programming Language (Swift 3.1) : Automatic Reference Counting
What is the difference in Swift between 'unowned(safe)' and 'unowned(unsafe)'?
F. 知乎上的一個怎麼面試iOS工程師的問題
1.什麼是arc?(arc是為了解決什麼問題誕生的?)
首先解釋ARC: automatic reference counting自動引用計數。
ARC幾個要點:
在對象被創建時 retain count +1,在對象被release時 retain count -1.當retain count 為0 時,銷毀對象。
程序中加入autoreleasepool的對象會由系統自動加上autorelease方法,如果該對象引用計數為0,則銷毀。
那麼ARC是為了解決什麼問題誕生的呢?這個得追溯到MRC手動內存管理時代說起。
MRC下內存管理的缺點:
1.當我們要釋放一個堆內存時,首先要確定指向這個堆空間的指針都被release了。(避免提前釋放)
2.釋放指針指向的堆空間,首先要確定哪些指針指向同一個堆,這些指針只能釋放一次。(MRC下即誰創建,誰釋放,避免重復釋放)
3.模塊化操作時,對象可能被多個模塊創建和使用,不能確定最後由誰去釋放。
4.多線程操作時,不確定哪個線程最後使用完畢
2.請解釋以下keywords的區別: assign vs weak, __block vs __weak
assign適用於基本數據類型,weak是適用於NSObject對象,並且是一個弱引用。
assign其實也可以用來修飾對象,那麼我們為什麼不用它呢?因為被assign修飾的對象在釋放之後,指針的地址還是存在的,也就是說指針並沒有被置為nil。如果在後續的內存分配中,剛好分到了這塊地址,程序就會崩潰掉。
而weak修飾的對象在釋放之後,指針地址會被置為nil。所以現在一般弱引用就是用weak。
首先__block是用來修飾一個變數,這個變數就可以在block中被修改(參考block實現原理)
__block:使用__block修飾的變數在block代碼快中會被retain(ARC下,MRC下不會retain)
__weak:使用__weak修飾的變數不會在block代碼塊中被retain
同時,在ARC下,要避免block出現循環引用 __weak typedof(self)weakSelf = self;
3.__block在arc和非arc下含義一樣嗎?
是不一樣的。
在MRC中__block variable在block中使用是不會retain的
但是ARC中__block則是會Retain的。
取而代之的是用__weak或是__unsafe_unretained來更精確的描述weak reference的目的
其中前者只能在iOS5之後可以使用,但是比較好 (該物件release之後,此pointer會自動設成nil)
而後者是ARC的環境下為了相容4.x的解決方案。
所以上面的範例中
__block MyClass* temp = …; // MRC環境下使用
__weak MyClass* temp = …; // ARC但只支援iOS5.0以上的版本
__unsafe_retained MyClass* temp = …; //ARC且可以相容4.x以後的版本
4.使用nonatomic一定是線程安全的嗎?()
不是的。
atomic原子操作,系統會為setter方法加鎖。 具體使用 @synchronized(self){//code }
nonatomic不會為setter方法加鎖。
atomic:線程安全,需要消耗大量系統資源來為屬性加鎖
nonatomic:非線程安全,適合內存較小的移動設備
5.描述一個你遇到過的retain cycle例子。
block中的循環引用:一個viewController
@property (nonatomic,strong)HttpRequestHandler * handler;
@property (nonatomic,strong)NSData *data;
_handler = [httpRequestHandler sharedManager];
[ downloadData:^(id responseData){
_data = responseData;
}];
1
2
3
4
5
6
self 擁有_handler, _handler 擁有block, block擁有self(因為使用了self的_data屬性,block會 一份self)
解決方法:
__weak typedof(self)weakSelf = self
[ downloadData:^(id responseData){
weakSelf.data = responseData;
}];
1
2
3
4
6.+(void)load; +(void)initialize;有什麼用處?
在Objective-C中,runtime會自動調用每個類的兩個方法。+load會在類初始載入時調用,+initialize會在第一次調用類的類方法或實例方法之前被調用。這兩個方法是可選的,且只有在實現了它們時才會被調用。
共同點:兩個方法都只會被調用一次。
7.為什麼其他語言里叫函數調用, objective c里則是給對象發消息(或者談下對runtime的理解)
先來看看怎麼理解發送消息的含義:
曾經覺得Objc特別方便上手,面對著 Cocoa 中大量 API,只知道簡單的查文檔和調用。還記得初學 Objective-C 時把[receiver message]當成簡單的方法調用,而無視了「發送消息」這句話的深刻含義。於是[receiver message]會被編譯器轉化為:
objc_msgSend(receiver, selector)
如果消息含有參數,則為:
objc_msgSend(receiver, selector, arg1, arg2, ...)
如果消息的接收者能夠找到對應的selector,那麼就相當於直接執行了接收者這個對象的特定方法;否則,消息要麼被轉發,或是臨時向接收者動態添加這個selector對應的實現內容,要麼就乾脆玩完崩潰掉。
現在可以看出[receiver message]真的不是一個簡簡單單的方法調用。因為這只是在編譯階段確定了要向接收者發送message這條消息,而receive將要如何響應這條消息,那就要看運行時發生的情況來決定了。
Objective-C 的 Runtime 鑄就了它動態語言的特性,這些深層次的知識雖然平時寫代碼用的少一些,但是卻是每個 Objc 程序員需要了解的。
Objc Runtime使得C具有了面向對象能力,在程序運行時創建,檢查,修改類、對象和它們的方法。可以使用runtime的一系列方法實現。
G. VC++的編譯器中,運算符new底層的實現是什麼
和+, -, *, /差不多,都是語言內部機制。可能是用C和匯編寫的,直接操作硬體或操作系統。
回復:
暈,上面的五級的經理,看你級別挺高,怎麼不給個像樣的答案呢?說的跟算命先生似的。什麼也沒有。。
確實是說得簡單了。
不同編譯器的實現是不同的,現在給一個實現,只有一部分,其他的自己看gcc的源代碼。
#include <bits/c++config.h>
#include <cstdlib>
#include <exception_defines.h>
#include "new"
using std::new_handler;
using std::bad_alloc;
#if _GLIBCXX_HOSTED
using std::malloc;
#else
extern "C" void *malloc (std::size_t);
#endif
extern new_handler __new_handler;
_GLIBCXX_WEAK_DEFINITION void *
operator new (std::size_t sz) throw (std::bad_alloc)
{
void *p;
/* malloc (0) is unpredictable; avoid it. */
if (sz == 0)
sz = 1;
p = (void *) malloc (sz);
while (p == 0)
{
new_handler handler = __new_handler;
if (! handler)
#ifdef __EXCEPTIONS
throw bad_alloc();
#else
std::abort();
#endif
handler ();
p = (void *) malloc (sz);
}
return p;
}
H. Object C 屬性、特性、類型
@property聲明屬性,做了三件事
.h: 聲明了getter和setter方法;
.h: 聲明了實例變數(默認:下劃線+屬性名);
.m: 實現了getter和setter方法。
是否給setter和getter加鎖,是否保證setter或者getter的每次訪問是完整性的
atomic (默認值)
一定程度上可以保證線程安全,有線程在訪問setter,其他線程只能等待完成後才能訪問。
nonatomic
不保證你獲得的是有效值
readwrite,就是告訴編譯器,同時生成getter和setter。如果是readonly,只生成getter。
readwrite: 「讀寫」
readonly: 「只讀」
strong (默認值)
表明你需要引用(持有)這個對象,負責保持這個對象的生命周期。
基本數據類型(非對象類型,如int, float, BOOL),默認值並不是strong,strong只能用於對象類型。
weak
跟strong相反,屬性所指的對象銷毀時,屬性值也會清空,設置為nil。
會給你一個引用,指向對象。但是不會主張所有權。也不會增加retain count。
在delegate patterns中常用weak解決strong reference cycles(以前叫retain cycles)問題。
會在賦值前,復制一個對象,指向新對象
NSString,NSArray,NSDictonary,推薦使用屬性
NSMubtableString,NSMutableArray, NSMutableDictonary屬性則使用strong屬性。
assign
針對基本數據類型賦值操作。
nullable :對象「可為空」
nonnull :對象「不可為空」
null_unspecified :「未指定」
null_resettable :調用setter去reset屬性時,可以傳入nil,但是getter返回值,不為空。
① 四種整型 :
short int : 短整型, 佔16位, mac 上占 2 位元組, ios 上占 2 位元組, 范圍 -32768(-2^15) ~ 32767(2^15 - 1), 3萬;
int : 整型, 佔32位, mac 上占 4 位元組, ios 上占 4 位元組, 范圍 -2147483648(-2^31) ~ 2147483647(2^31 - 1), 21億;
long int : 長整型, 佔64位, mac 上占 8 位元組, ios 上占 4 位元組, (-2^63) ~ (2^63 - 1), 922億億;
long long : 佔64位, mac 上占 8 位元組, ios 上占 8 位元組;
② 進制 :
八進制 十六進制賦值 : 八進制由 "0" 開頭, 十六進制由 "0x" 或者 "0X" 開頭;
③ 無符號整型:
無符號整型 第一位 不是符號位, 范圍比原來要大,例 unsigned short int 范圍是 0到6萬
-- %d : 十進制整數, 正數無符號, 負數有 "-" 符號;
-- %o : 八進制無符號整數, 沒有 0 前綴;
-- %x : 十六進制無符號整數, 沒有 0x 前綴;
-- %u : 十進制無符號整數;
單個字元表示 : 使用 '' 將單個字元引起來, 便是字元常量, 如 'a', 'b' 等;
轉義字元表示 : 使用轉義符號 \ 來指定字元, 如 '\n' 等;
字元佔用空間大小 : 每個字元佔用一個位元組, 因此 Object-C 不支持中文字元, 因為中文字元都是占 2 ~ 3 個位元組;
-- %c : 單個字元輸出;
-- %s : 輸出字元串;
float : 占 4 位元組;
double : 占 8 位元組;
long double : 占 16 位元組;
CGFloat :對於需要兼容64位機器的程序而言,需要用CGFloat,當然從長遠角度考慮還是推薦盡量使用CGFloat。盡管在32位上相比float增加了一些memory footprint的消耗
(Object-C浮點數 : 不區分 double 與 float, 一個浮點數 3.8 可以賦值給兩種類型的變數)
-- %f : 以小數形式輸出浮點數, 默認 6 位小數;
-- %e : 以指數形式輸出浮點數, 默認 6 位小數;
-- %g : 自動選擇 %e 或者 %f 各式;
① 定義普通枚舉:
定義方式 : 格式 enum enum_name {elem1, elem2, elem3 ...};
示例 : enum day{Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday};
定義枚舉變數 : 格式 enum enum_name var1, var2;
示例 : enum day today, tomorrow, 注意 today tomorrow 兩個變數的取值只能是 day 枚舉中定義的值;
枚舉變數賦值 : 格式 variable = elm1 ;
示例 : today = Sunday; tomorrow = Friday;
② 定義匿名枚舉:
匿名枚舉格式 : enum {Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday} today, tomorrow ;
說明 : 兩個枚舉變數 today 和 tomorrow 只能取值 enum 中得枚舉值;
③ 枚舉值簡介
枚舉值常量 : 在 {} 中得是枚舉常量 或者 枚舉元素, 該元素不是變數, 不能對齊進行賦值, 枚舉的值按照定義的順序 0, 1, 2, 3 以此類推;
枚舉值本質 : 枚舉值屬於無符號整數, 可以使用 %u 佔位符列印出來, 其值也能進行大小比較, 和四則運算;
枚舉初值 : 枚舉值可以在定義的時候賦予一個初值;
-- %p : 輸出十六進制形式的指針地址;
-- %@ : 輸出 Object-C 對象;
BOOL 類型值 : 該類型至右兩個值 YES 和 NO ;
BOOL 類型本質 : 該類型本質是 signed char, YES 是 1, NO 是 0, 在處理的時候 YES 會被當成真處理, NO 會被當成假處理;
nil相當於Java中的null,表示一個對象,這個對象的指針指向空。Nil是定義一個指向空的類而不是對象。
可以存放任何數據類型的對象,類似Java中的Object類,其被定義為指向對象的指針(本身就是指針了),故定義比如id instance = nil;
id類型是多態和動態綁定的基礎。
Object C 數字、字元串、集合、字典、NSURL、NSDate
下一章: Object C 數字、字元串、集合、字典、NSURL、NSDate - (jianshu.com)
I. c++ if .……else 大家幫幫看看什麼問題,謝謝!
編的沒問題
解決出現fatal error LNK1169: one or more multiply(轉)
大家都知道,從C/C++源程序到可執行文件要經歷兩個階段:(1)編譯器將源文件編譯成匯編代碼,然後由匯編器 (assembler)翻譯成機器指令(再加上其它相關信息)後輸出到一個個目標文件(object file,VC的編譯器編譯出的目標文件默認的後綴名是.obj)中;(2)鏈接器(linker)將一個個的目標文件(或許還會有若干程序庫)鏈接在一起生成一個完整的可執行文件。
編譯器編譯源文件時會把源文件的全局符號(global symbol)分成強(strong)和弱(weak)兩類傳給匯編器,而隨後匯編器則將強弱信息編碼並保存在目標文件的符號表中。那麼何謂強弱呢?編譯器認為函數與初始化了的全局變數都是強符號,而未初始化的全局變數則成了弱符號。比如有這么個源文件:
extern int errorno;
int buf[2] = {1,2};
int *p;
int main()
{
return 0;
}
其中main、buf是強符號,p是弱符號,而errorno則非強非弱,因為它只是個外部變數的使用聲明。
有了強弱符號的概念,我們就可以看看鏈接器是如何處理與選擇被多次定義過的全局符號:
規則1: 不允許強符號被多次定義(即不同的目標文件中不能有同名的強符號);
規則2: 如果一個符號在某個目標文件中是強符號,在其它文件中都是弱符號,那麼選擇強符號;
規則3: 如果一個符號在所有目標文件中都是弱符號,那麼選擇其中任意一個;
由上可知多個目標文件不能重復定義同名的函數與初始化了的全局變數,否則必然導致LNK2005和LNK1169兩種鏈接錯誤。可是,有的時候我們並沒有在自己的程序中發現這樣的重定義現象,卻也遇到了此種鏈接錯誤,這又是何解?嗯,問題稍微有點兒復雜,容我慢慢道來。
眾所周知,ANSI C/C++ 定義了相當多的標准函數,而它們又分布在許多不同的目標文件中,如果直接以目標文件的形式提供給程序員使用的話,就需要他們確切地知道哪個函數存在於哪個目標文件中,並且在鏈接時顯式地指定目標文件名才能成功地生成可執行文件,顯然這是一個巨大的負擔。所以C語言提供了一種將多個目標文件打包成一個文件的機制,這就是靜態程序庫(static library)。開發者在鏈接時只需指定程序庫的文件名,鏈接器就會自動到程序庫中尋找那些應用程序確實用到的目標模塊,並把(且只把)它們從庫中拷貝出來參與構建可執行文件。幾乎所有的C/C++開發系統都會把標准函數打包成標准庫提供給開發者使用(有不這么做的嗎?)。
程序庫為開發者帶來了方便,但同時也是某些混亂的根源。我們來看看鏈接器是如何解析(resolve)對程序庫的引用的。
在符號解析(symbol resolution)階段,鏈接器按照所有目標文件和庫文件出現在命令行中的順序從左至右依次掃描它們,在此期間它要維護若干個集合:(1)集合E是將被合並到一起組成可執行文件的所有目標文件集合;(2)集合U是未解析符號(unresolved symbols,比如已經被引用但是還未被定義的符號)的集合;(3)集合D是所有之前已被加入到E的目標文件定義的符號集合。一開始,E、U、D都是空的。
(1): 對命令行中的每一個輸入文件f,鏈接器確定它是目標文件還是庫文件,如果它是目標文件,就把f加入到E,並把f中未解析的符號和已定義的符號分別加入到U、D集合中,然後處理下一個輸入文件。
(2): 如果f是一個庫文件,鏈接器會嘗試把U中的所有未解析符號與f中各目標模塊定義的符號進行匹配。如果某個目標模塊m定義了一個U中的未解析符號,那麼就把 m加入到E中,並把m中未解析的符號和已定義的符號分別加入到U、D集合中。不斷地對f中的所有目標模塊重復這個過程直至到達一個不動點(fixed point),此時U和D不再變化。而那些未加入到E中的f里的目標模塊就被簡單地丟棄,鏈接器繼續處理下一輸入文件。
(3): 如果處理過程中往D加入一個已存在的符號,或者當掃描完所有輸入文件時U非空,鏈接器報錯並停止動作。否則,它把E中的所有目標文件合並在一起生成可執行文件。
VC帶的編譯器名字叫cl.exe,它有這么幾個與標准程序庫有關的選項: /ML、/MLd、/MT、/MTd、/MD、/MDd。這些選項告訴編譯器應用程序想使用什麼版本的C標准程序庫。/ML(預設選項)對應單線程靜態版的標准程序庫(libc.lib);/MT對應多線程靜態版標准庫(libcmt.lib),此時編譯器會自動定義_MT宏;/MD對應多線程DLL版 (導入庫msvcrt.lib,DLL是msvcrt.dll),編譯器自動定義_MT和_DLL兩個宏。後面加d的選項都會讓編譯器自動多定義一個 _DEBUG宏,表示要使用對應標准庫的調試版,因此/MLd對應調試版單線程靜態標准庫(libcd.lib),/MTd對應調試版多線程靜態標准庫 (libcmtd.lib),/MDd對應調試版多線程DLL標准庫(導入庫msvcrtd.lib,DLL是msvcrtd.dll)。雖然我們的確在編譯時明白無誤地告訴了編譯器應用程序希望使用什麼版本的標准庫,可是當編譯器幹完了活,輪到鏈接器開工時它又如何得知一個個目標文件到底在思念誰?為了傳遞相思,我們的編譯器就幹了點秘密的勾當。在cl編譯出的目標文件中會有一個專門的區域(關心這個區域到底在文件中什麼地方的朋友可以參考COFF和 PE文件格式)存放一些指導鏈接器如何工作的信息,其中有一種就叫預設庫(default library),這些信息指定了一個或多個庫文件名,告訴鏈接器在掃描的時候也把它們加入到輸入文件列表中(當然順序位於在命令行中被指定的輸入文件之後)。說到這里,我們先來做個小實驗。寫個頂頂簡單的程序,然後保存為main.c :
/* main.c */
int main() { return 0; }
用下面這個命令編譯main.c(什麼?你從不用命令行來編譯程序?這個......) :
cl /c main.c
/c是告訴cl只編譯源文件,不用鏈接。因為/ML是預設選項,所以上述命令也相當於: cl /c /ML main.c 。如果沒什麼問題的話(要出了問題才是活見鬼!當然除非你的環境變數沒有設置好,這時你應該去VC的bin目錄下找到vcvars32.bat文件然後運行它。),當前目錄下會出現一個main.obj文件,這就是我們可愛的目標文件。隨便用一個文本編輯器打開它(是的,文本編輯器,大膽地去做別害怕),搜索"defaultlib"字元串,通常你就會看到這樣的東西: "-defaultlib:LIBC -defaultlib:OLDNAMES"。啊哈,沒錯,這就
是保存在目標文件中的預設庫信息。我們的目標文件顯然指定了兩個預設庫,一個是單線程靜態版標准庫libc.lib(這與/ML選項相符),另外一個是oldnames.lib(它是為了兼容微軟以前的C/C++開發系統)。
VC的鏈接器是link.exe,因為main.obj保存了預設庫信息,所以可以用
link main.obj libc.lib
或者
link main.obj
來生成可執行文件main.exe,這兩個命令是等價的。但是如果你用
link main.obj libcd.lib
的話,鏈接器會給出一個警告: "warning LNK4098: defaultlib "LIBC" conflicts with use of other libs; use /NODEFAULTLIB:library",因為你顯式指定的標准庫版本與目標文件的預設值不一致。通常來說,應該保證鏈接器合並的所有目標文件指定的預設標准庫版本一致,否則編譯器一定會給出上面的警告,而LNK2005和LNK1169鏈接錯誤則有時會出現有時不會。那麼這個有時到底是什麼時候?呵呵,別著急,下面的一切正是為喜歡追根究底的你准備的。
建一個源文件,就叫mylib.c,內容如下:
/* mylib.c */
#i nclude
void foo()
{
printf("%s","I am from mylib!\n");
}
用
cl /c /MLd mylib.c
命令編譯,注意/MLd選項是指定libcd.lib為默認標准庫。lib.exe是VC自帶的用於將目標文件打包成程序庫的命令,所以我們可以用
lib /OUT:my.lib mylib.obj
將mylib.obj打包成庫,輸出的庫文件名是my.lib。接下來把main.c改成:
/* main.c */
void foo();
int main()
{
foo();
return 0;
}
用
cl /c main.c
編譯,然後用
link main.obj my.lib
進行鏈接。這個命令能夠成功地生成main.exe而不會產生LNK2005和LNK1169鏈接錯誤,你僅僅是得到了一條警告信息:"warning LNK4098: defaultlib "LIBCD" conflicts with use of other libs; use /NODEFAULTLIB:library"。我們根據前文所述的掃描規則來分析一下鏈接器此時做了些啥。
一開始E、U、D都是空集,鏈接器首先掃描到main.obj,把它加入E集合,同時把未解析的foo加入U,把main加入D,而且因為 main.obj的默認標准庫是libc.lib,所以它被加入到當前輸入文件列表的末尾。接著掃描my.lib,因為這是個庫,所以會拿當前U中的所有符號(當然現在就一個foo)與my.lib中的所有目標模塊(當然也只有一個mylib.obj)依次匹配,看是否有模塊定義了U中的符號。結果 mylib.obj確實定義了foo,於是它被加入到E,foo從U轉移到D,mylib.obj引用的printf加入到U,同樣地,mylib.obj指定的默認標准庫是libcd.lib,它也被加到當前輸入文件列表的末尾(在libc.lib的後面)。不斷地在my.lib庫的各模塊上進行迭代以匹配U中的符號,直到U、D都不再變化。很明顯,現在就已經到達了這么一個不動點,所以接著掃描下一個輸入文件,就是 libc.lib。鏈接器發現libc.lib里的printf.obj里定義有printf,於是printf從U移到D,而printf.obj被加入到E,它定義的所有符號加入到D,它里頭的未解析符號加入到U。鏈接器還會把每個程序都要用到的一些初始化操作所在的目標模塊(比如crt0.obj 等)及它們所引用的模塊(比如malloc.obj、free.obj等)自動加入到E中,並更新U和D以反應這個變化。事實上,標准庫各目標模塊里的未解析符號都可以在庫內其它模塊中找到定義,因此當鏈接器處理完libc.lib時,U一定是空的。最後處理libcd.lib,因為此時U已經為空,所以鏈接器會拋棄它裡面的所有目標模塊從而結束掃描,然後合並E中的目標模塊並輸出可執行文件。
上文描述了雖然各目標模塊指定了不同版本的預設標准庫但仍然鏈接成功的例子,接下來你將目睹因為這種不嚴謹而導致的悲慘失敗。
修改mylib.c成這個樣子:
#i nclude
void foo()
{
// just a test , don"t care memory leak
_malloc_dbg( 1, _NORMAL_BLOCK, __FILE__, __LINE__ );
}
其中_malloc_dbg不是ANSI C的標准庫函數,它是VC標准庫提供的malloc的調試版,與相關函數配套能幫助開發者抓各種內存錯誤。使用它一定要定義_DEBUG宏,否則預處理器會把它自動轉為malloc。繼續用
cl /c /MLd mylib.c
lib /OUT:my.lib mylib.obj
編譯打包。當再次用
link main.obj my.lib
進行鏈接時,我們看到了什麼?天哪,一堆的LNK2005加上個貴為"fatal error"的LNK1169墊底,當然還少不了那個LNK4098。鏈接器是不是瘋了?不,你冤枉可憐的鏈接器了,我拍胸脯保證它可是一直在盡心盡責地照章辦事。
一開始E、U、D為空,鏈接器掃描main.obj,把它加入E,把foo加入U,把main加入D,把libc.lib加入到當前輸入文件列表的末尾。接著掃描my.lib,foo從U轉移到D,_malloc_dbg加入到U,libcd.lib加到當前輸入文件列表的尾部。然後掃描 libc.lib,這時會發現libc.lib里任何一個目標模塊都沒有定義_malloc_dbg(它只在調試版的標准庫中存在),所以不會有任何一個模塊因為_malloc_dbg而加入E,但是每個程序都要用到的初始化模塊(如crt0.obj等)及它們所引用的模塊(比如malloc.obj、 free.obj等)還是會自動加入到E中,同時U和D被更新以反應這個變化。當鏈接器處理完libc.lib時,U只剩_malloc_dbg這一個符號。最後處理libcd.lib,發現dbgheap.obj定義了_malloc_dbg,於是dbgheap.obj加入到E,它里頭的未解析符號加入U,它定義的所有其它符號也加入D,這時災難便來了。之前malloc等符號已經在D中(隨著libc.lib里的malloc.obj加入E而加入的),而dbgheap.obj又定義了包括malloc在內的許多同名符號,這引發了重定義沖突,鏈接器只好中斷工作並報告錯誤。
現在我們該知道,鏈接器完全沒有責任,責任在我們自己的身上。是我們粗心地把預設標准庫版本不一致的目標文件(main.obj)與程序庫 (my.lib)鏈接起來,導致了大災難。解決辦法很簡單,要麼用/MLd選項來重編譯main.c;要麼用/ML選項重編譯mylib.c。
在上述例子中,我們擁有庫my.lib的源代碼(mylib.c),所以可以用不同的選項重新編譯這些源代碼並再次打包。可如果使用的是第三方的庫,它並沒有提供源代碼,那麼我們就只有改變自己程序的編譯選項來適應這些庫了。但是如何知道庫中目標模塊指定的默認庫呢?其實VC提供的一個小工具便可以完成任務,這就是mpbin.exe。運行下面這個命令
mpbin /DIRECTIVES my.lib
然後在輸出中找那些"Linker Directives"引導的信息,你一定會發現每一處這樣的信息都會包含若干個類似"-defaultlib:XXXX"這樣的字元串,其中XXXX便代表目標模塊指定的預設庫名。
知道了第三方庫指定的默認標准庫,再用合適的選項編譯我們的應用程序,就可以避免LNK2005和LNK1169鏈接錯誤。喜歡IDE的朋友,你一樣可以到 "Project屬性" -> "C/C++" -> "代碼生成(code generation)" -> "運行時庫(run-time library)" 項下設置應用程序的默認標准庫版本,這與命令行選項的效果是一樣的。
終極解決辦法:
在 Project/Setting/Link/General中的 Project Options: 加入 /FORCE:MULTIPLE即可
J. map中的key為結構體時,怎麼find
第一個問題是關於 map 的。話不多說,以下 20 多行的 C++ 代碼重現了我遇到的問題:
#include <iostream>
#include <map>
using namespace std;
struct S {
int x, y;
S(int xx, int yy): x(xx), y(yy) {}
bool operator <(const S& s) const {
return x < s.x && y < s.y;
}
};
map<S, int > ms;
int main() {
ms.insert(map<S, int >::value_type(S(31, 41), 59));
S test(31, 59);
if (ms.find(test) != ms.end()) {
cout << "Find the value: " << ms[test] << endl;
} else {
cout << "Find Failure/n" ;
}
return 0;
}
使用 VC++6.0 , VC++2005 Express Edition, VC++2005 command line compiler( 不帶任何編譯選項 ) , g++ 測試的結果都相同,最後輸出:
Find the value: 59
這個問題比較隱蔽。多個編譯器測試結果相同說明肯定不是編譯器版本相關的問題。直接調試進入 find 函數就可以明白:
iterator find(const key_type& _Keyval)
{ // find an element in mutable sequence that matches _Keyval
iterator _Where = lower_bound(_Keyval);
return (_Where == end()
|| _DEBUG_LT_PRED(this ->comp,
_Keyval, _Key(_Where._Mynode()))
? end() : _Where);
}
雖然這樣調試會遇到一些 STL 內部的細節,但整體實現思路還是可看出來。在 find 函數中, lower_bound 返回值是結點 (31, 41) 。跟蹤進入,發現調用的 _DEBUG_LT_PRED 的定義如下:
#define _DEBUG_LT_PRED(pred, x, y) _Debug_lt_pred(pred, x, y, __FILEW__, __LINE__)
template <class _Pr, class _Ty1, class _Ty2> inline
bool __CLRCALL_OR_CDECL _Debug_lt_pred(_Pr _Pred, const _Ty1& _Left, const _Ty2& _Right,
const wchar_t *_Where, unsigned int _Line)
{ // test if _Pred(_Left, _Right) and _Pred is strict weak ordering
if (!_Pred(_Left, _Right))
return (false );
else if (_Pred(_Right, _Left))
_DEBUG_ERROR2("invalid operator<" , _Where, _Line);
return (true );
}
( 註:關於 _Debug_lt_pred 函數有三個重載版本,分別是針對參數 _Left, _Right 的 const 性質的,看這些代碼能學到很多東西。另外,如果靜態地看這些代碼來分析自己程序中的錯誤,則因為有大量的重載函數,所以靜態分析時很難自己確定到底哪一個函數被調用,而動態調試就能一步到位。 )
從這個函數的代碼里大致就能看出問題所在了。猜測這里的 _Pred 參數就是自己在 struct 里定義的那個 operator < ,編譯器中看到 _Pred 的 value 是 {lessthan } , type 是 std::less <S> ,但這里有更大的發現: strict weak ordering!!! 自己 C++ 功底很淺,這是一個新的發現,馬上 google 一下 」strict weak ordering」 這個關鍵詞,果然發現大量的專題鏈接!暫且先放下這個主題。問題猜測肯定是出在 operator < 這個函數上了,因為根據自己的 operator < 定義: {31, 41} < {31, 59} 返回值是 false , {31, 59} < {31, 41} 的返回值也是 false ,那麼,由這兩個比較能得出結論: {31, 41} == {31, 59} !!! 這也無怪乎程序運行會返回不期望的結果了。
但這也只是猜測,繼續調試,看能不能找到 _Pred 函數的真實面目。上面說了從編譯器中看出 _Pred 的 type 是 std::less <S> ,在 MSDN 中找到 less 是 STL 中的一個模板類,以下是在 MSDN 中看到的定義:
less
less
template<class T>
struct less
: public binary_function
<T, T, bool> {
bool operator()
(const T& x, const T& y) const;
};
The template class defines its member function as returning x < y . The member function defines a total ordering , even if T is an object pointer type.
我們接著調試,跟蹤進入 _Pred 函數,發現它的定義如下:
template <class _Ty>
struct less
: public binary_function<_Ty, _Ty, bool >
{ // functor for operator<
bool operator ()(const _Ty& _Left, const _Ty& _Right) const
{ // apply operator< to operands
return (_Left < _Right);
}
};
它最終比較 _Left 和 _Right 時調用的正是 struct S 中定義的 operator < 。
至此,問題真相大白。還遺留兩個主題:一個是關於 strict weak ordering ,另一個是 STL 中的一些實現方法,因為以上只是跟蹤調試過程把沿途看到的東西機械地記錄了下來,並不是真正的理解。
無獨有偶,今天遇到另一個問題,關於 STL 中的 sort 函數的問題,這個問題是唯獨在 VC++ 2005 Express Edition 中才出現的,並且在命令行下使用 cl.exe 不帶任何選項編譯連接時正常,使用 g++ 也正常。問題的表現就是程序在運行時出現異常,信息是: 」invalid operator <」 。這個問題就不再重現調試了,它的解決方法見下列地址:
http://support.microsoft.com/kb/949171
strict weak ordering 是一個數學上的術語,剛剛給出的這個地址上面有關於 strict weak ordering 的簡明的解釋,貼過來:
The STL algorithms for stable_sort ( ) and sort() require the binary predicate to be strict weak ordering.
For example:
· Strict: pred (X, X) is always false.
· Weak: If ! pred (X, Y) && !pred (Y, X), X==Y.
· Ordering: If pred (X, Y) && pred (Y, Z), then pred (X, Z).