1.第一條也是最重要的一條:隱藏css
當咱們同時編譯多個文件時,全部未加static前綴的全局變量和函數都具備全局可見性。若是加了static,就會對其它源文件隱藏。例如在a和msg的定義前加上static,main.c就看不到它們了。利用這一特性能夠在不一樣的文件中定義同名函數和同名變量,而沒必要擔憂命名衝突。Static能夠用做函數和變量的前綴,對於函數來說,static的做用僅限於隱藏html
2.static的第二個做用是保持變量內容的持久c++
3.static的第三個做用是默認初始化爲0程序員
https://blog.csdn.net/weixin_40237626/article/details/82313339算法
內存分配:指針是一個實體,須要分配內存空間。引用只是變量的別名,不須要分配內存空間。
初始化:引用在定義的時候必須進行初始化,而且不可以改變。指針在定義的時候不必定要初始化,而且指向的空間可變。
使用級別:有多級指針,可是沒有多級引用,只能一級引用。
自增運算:指針和引用的自增運算結果不同。(指針是指向下一個空間,引用時引用的變量值加1)
使用sizeof時:引用獲得的是所指向的變量(對象)的大小,而sizeof 指針獲得的是指針自己的大小。
直接與間接訪問:引用訪問一個變量是直接訪問,而指針訪問一個變量是間接訪問。
野指針:使用指針前最好作類型檢查,防止野指針的出現;
參數傳遞:做爲參數時,傳指針的實質是傳值,傳遞的值是指針的地址;傳引用的實質是傳地址,傳遞的是變量的地址。數據庫
不能夠,new對應delete不能夠張冠李戴。
malloc/free,new/delete必需配對使用。
malloc與free是c++、c語言的標準庫函數,new、delete是c++的運算符。它們均可用用申請動態內存和釋放內存。對於非內部數據類型的對象而言,光用malloc/free沒法知足動態對象的要求。對象在建立的同時要自動執行函數,對象在消亡以前要自動執行析構函數。因爲malloc/free是庫函數而不是運算符,不在編譯器控制權限以內,不可以把執行構造函數和析構函數的任務強加於malloc/free。所以c++語言須要一個能完成動態內存分配和初始化工做的運算符new,以及一個能完成清理與釋放內存工做的運算符delete。注意new/delete不是庫函數。編程
編譯的概念:編譯程序讀取源程序(字符流),對之進行詞法和語法的分析,將高級語言指令轉換爲功能等效的彙編代碼,再由彙編程序轉換爲機器語言,而且按照操做系統對可執行文件格式的要求連接生成可執行程序。
編譯的完整過程:C源程序-->預編譯處理(.c)-->編譯、優化程序(.asm、.s)-->彙編程序(.obj、.o、.a)-->連接程序(.exe等可執行文件)數組
1. 編譯預處理(Preprocess) 瀏覽器
讀取C源程序,對其中的僞指令(以#開頭的指令)和特殊符號進行處理。主要包括四類:宏定義、條件編譯指令、頭文件和特殊符號。
預編譯程序所完成的基本上是對源程序的「替代」工做。通過此種替代,生成一個沒有宏定義、沒有條件編譯指令、沒有特殊符號的輸出文件。這個文件的含義同沒有通過預處理的源文件是相同的,但內容有所不一樣。緩存
2. 編譯、優化階段(Compile)
通過預編譯獲得的輸出文件中,只有常量;如數字、字符串、變量的定義,以及C語言的關鍵字,如main,if,else,for,while,{,}, +,-,*,/等等。
編譯程序所要做得工做就是經過詞法分析和語法分析,在確認全部的指令都符合語法規則以後,將其翻譯成等價的中間代碼表示或彙編代碼。
優化處理是編譯系統中一項比較艱深的技術。它涉及到的問題不只同編譯技術自己有關,並且同機器的硬件環境也有很大的關係。優化一部分是對中間代碼的優化。這種優化不依賴於具體的計算機。另外一種優化則主要針對目標代碼的生成而進行的。
對於前一種優化,主要的工做是刪除公共表達式、循環優化(代碼外提、強度削弱、變換循環控制條件、已知量的合併等)、複寫傳播,以及無用賦值的刪除,等等。
後一種類型的優化同機器的硬件結構密切相關,最主要的是考慮是如何充分利用機器的各個硬件寄存器存放的有關變量的值,以減小對於內存的訪問次數。另外,如何根據機器硬件執行指令的特色(如流水線、RISC、CISC、VLIW等)而對指令進行一些調整使目標代碼比較短,執行的效率比較高,也是一個重要的研究課題。
通過優化獲得的彙編代碼必須通過彙編程序的彙編轉換成相應的機器指令,方可能被機器執行。
3. 彙編過程(Assemble)
彙編過程實際上指把彙編語言代碼翻譯成目標機器指令的過程。對於被翻譯系統處理的每個C語言源程序,都將最終通過這一處理而獲得相應的目標文件。目標文件中所存放的也就是與源程序等效的目標的機器語言代碼。
目標文件由段組成。一般一個目標文件中至少有兩個段:
代碼段:該段中所包含的主要是程序的指令。該段通常是可讀和可執行的,但通常卻不可寫。
數據段:主要存放程序中要用到的各類全局變量或靜態的數據。通常數據段都是可讀,可寫,可執行的。
Win32平臺上通常生成.obj文件,其擁有PE(Portable Executable,即Windows可執行文件)文件格式,包含的是二進制代碼,可是不必定能執行。當編譯器將一個工程裏的全部.cpp文件以分離的方式編譯完畢後,再由連接器進行連接成爲一個.exe或.dll文件。
4. 連接程序(Link)
由彙編程序生成的目標文件並不能當即就被執行,其中可能還有許多沒有解決的問題。
例如,某個源文件中的函數可能引用了另外一個源文件中定義的某個符號(如變量或者函數調用等);在程序中可能調用了某個庫文件中的函數,等等。全部的這些問題,都須要經連接程序的處理方能得以解決。
連接程序的主要工做就是將有關的目標文件彼此相鏈接,也即將在一個文件中引用的符號同該符號在另一個文件中的定義鏈接起來,使得全部的這些目標文件成爲一個可以誒操做系統裝入執行的統一總體。
根據開發人員指定的同庫函數的連接方式的不一樣,連接處理
可分爲兩種:
(1)靜態連接
在這種連接方式下,函數的代碼將從其所在地靜態連接庫中被拷貝到最終的可執行程序中。這樣該程序在被執行時這些代碼將被裝入到該進程的虛擬地址空間中。靜態連接庫其實是一個目標文件的集合,其中的每一個文件含有庫中的一個或者一組相關函數的代碼。
(2)動態連接
在此種方式下,函數的代碼被放到稱做是動態連接庫或共享對象的某個目標文件中。連接程序此時所做的只是在最終的可執行程序中記錄下共享對象的名字以及其它少許的登記信息。在此可執行文件被執行時,動態連接庫的所有內容將被映射到運行時相應進程的虛地址空間。動態連接程序將根據可執行程序中記錄的信息找到相應的函數代碼。
對於可執行文件中的函數調用,可分別採用動態連接或靜態連接的方法。使用動態連接可以使最終的可執行文件比較短小,而且當共享對象被多個進程使用時能節約一些內存,由於在內存中只須要保存一份此共享對象的代碼。但並非使用動態連接就必定比使用靜態連接要優越。在某些狀況下動態連接可能帶來一些性能上損害。
https://blog.csdn.net/q_l_s/article/details/52608734
1)須要頻繁建立銷燬的優先用線程
2)須要進行大量計算的優先使用線程
所謂大量計算,固然就是要耗費不少CPU,切換頻繁了,這種狀況下線程是最合適的。
https://blog.csdn.net/w00w12l/article/details/45077821
https://www.zhihu.com/question/23356564
TCP的特色:
從原理上,TCP的優點有:
TCP最糟糕的特性是它對阻塞的控制。通常來講,TCP假定丟包是因爲網絡帶寬不夠形成的,因此發生這種狀況的時候,TCP就會減小發包速度。
在3G或WiFi下,一個數據包丟失了,你但願的是立馬重發這個數據包,然而TCP的阻塞機制卻徹底是採用相反的方式來處理!
並且沒有任何辦法可以繞過這個機制,由於這是TCP協議構建的基礎。這就是爲何在3G或者WiFi環境下,ping值可以上升到1000多毫秒的緣由。
UDP的特色:
UDP相對TCP來講既簡單又困難。
舉個例子來講,UDP是基於數據包構建,這意味着在某些方面須要你徹底顛覆在TCP下的觀念。UDP只使用一個socket進行通訊,不像TCP須要爲每個客戶端創建一個socket鏈接。這些都是UDP很是不錯的地方。
可是,大多數狀況下你須要的僅僅是一些鏈接的概念罷了,一些基本的包序功能,以及所謂的鏈接可靠性。惋惜的是,這些功能UDP都沒有辦法簡單的提供給你,而你使用TCP卻均可以避免費獲得。
使用TCP失敗的地方:
可靠的UDP也是有延遲的,可是因爲它是在UDP的基礎之上創建的通訊協議,因此能夠經過多種方式來減小延遲,不像TCP,全部的東西都要依賴於TCP協議自己而沒法被更改。
魔獸世界以及其餘的一些遊戲是怎麼處理延遲問題的呢?
一些相似發起攻擊動做和釋放技能特效就可以在沒有收到服務器確認的狀況下就直接執行,好比展示冰凍技能的效果就能夠在服務器沒有返回數據前在客戶端就作出來。
客戶端直接開始進行計算而不等待服務端確認是一種典型的隱藏延遲的技術。
這也意味着,咱們究竟是使用TCP仍是UDP取決於咱們可否隱藏延遲。
一個採用TCP的遊戲必須可以處理好突發的延遲問題(紙牌客戶端就很典型,對突發性的一秒的延遲,玩家也不會產生什麼抱怨)或者是擁有緩解延遲問題的好方法。
若是你運行的是一個沒法使用任何減緩延遲措施的遊戲呢?玩家對玩家的動做類遊戲一般就屬於這個範疇,可是這也不只僅限於動做類遊戲。
一種常見的操做是,你快速的移動你的角色經過一張充滿戰爭迷霧的世界地圖,可是一旦你探索過,迷霧就會被打開。
爲了確保遊戲的規則,防止玩家做弊,服務器只能顯示玩家當前位置附近的信息。這意味着不像魔獸世界,玩家沒法在沒有獲得服務器響應的狀況下,作出完整的動做。戰爭迷霧的探開必須依靠服務器傳過來的響應。
而當出現丟包時,因爲擁塞控制的緣由,服務器的響應速度就從100-150毫秒上升到1000-2000毫秒
沒有任何辦法能夠繞過TCP的這個設置來避開這個問題。
咱們替換了TCP的代碼,用了自定義的可靠的UDP來實現,把大量的丟包產生的延遲降到了僅僅只有50毫秒,甚至比之前TCP不丟包的狀況一個來回的延遲還要小。固然,這隻可能創建在UDP之上,這樣咱們纔對可靠性擁有徹底的掌控力。
可靠的UDP一點也不像TCP,要去實現一個特殊的阻塞控制。事實上,這也是你使用可靠UDP代替TCP的最大的緣由,避免TCP的阻塞控制。
那麼究竟是用UDP仍是TCP呢?
必要條件
互斥:每一個資源要麼已經分配給了一個進程,要麼就是可用的。
佔有和等待:已經獲得了某個資源的進程能夠再請求新的資源。
不可搶佔:已經分配給一個進程的資源不能強制性地被搶佔,它只能被佔有它的進程顯式地釋放。
環路等待:有兩個或者兩個以上的進程組成一條環路,該環路中的每一個進程都在等待下一個進程所佔有的資源。
處理方法
主要有如下四種方法:
鴕鳥策略
死鎖檢測與死鎖恢復
死鎖預防
死鎖避免
緩存處理,在HTTP1.0中主要使用header裏的If-Modified-Since,Expires來作爲緩存判斷的標準,HTTP1.1則引入了更多的緩存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供選擇的緩存頭來控制緩存策略。
帶寬優化及網絡鏈接的使用,HTTP1.0中,存在一些浪費帶寬的現象,例如客戶端只是須要某個對象的一部分,而服務器卻將整個對象送過來了,而且不支持斷點續傳功能,HTTP1.1則在請求頭引入了range頭域,它容許只請求資源的某個部分,即返回碼是206(Partial Content),這樣就方便了開發者自由的選擇以便於充分利用帶寬和鏈接。
錯誤通知的管理,在HTTP1.1中新增了24個錯誤狀態響應碼,如409(Conflict)表示請求的資源與資源的當前狀態發生衝突;410(Gone)表示服務器上的某個資源被永久性的刪除。
Host頭處理,在HTTP1.0中認爲每臺服務器都綁定一個惟一的IP地址,所以,請求消息中的URL並無傳遞主機名(hostname)。但隨着虛擬主機技術的發展,在一臺物理服務器上能夠存在多個虛擬主機(Multi-homed Web Servers),而且它們共享一個IP地址。HTTP1.1的請求消息和響應消息都應支持Host頭域,且請求消息中若是沒有Host頭域會報告一個錯誤(400 Bad Request)。
長鏈接,HTTP 1.1支持長鏈接(PersistentConnection)和請求的流水線(Pipelining)處理,在一個TCP鏈接上能夠傳送多個HTTP請求和響應,減小了創建和關閉鏈接的消耗和延遲,在HTTP1.1中默認開啓Connection: keep-alive,必定程度上彌補了HTTP1.0每次請求都要建立鏈接的缺點。
HTTP/1.x 缺陷
HTTP/1.x 實現簡單是以犧牲性能爲代價的:
客戶端須要使用多個鏈接才能實現併發和縮短延遲;
不會壓縮請求和響應首部,從而致使沒必要要的網絡流量;
不支持有效的資源優先級,導致底層 TCP 鏈接的利用率低下。
HTTP2.0與HTTP1.x的區別:
1.二進制分幀層
HTTP/2.0 將報文分紅 HEADERS 幀和 DATA 幀(一個報文拆分紅兩部分分別傳送),它們都是二進制格式的,HTTP1.x的解析是基於文本。。
在通訊過程當中,只會有一個 TCP 鏈接存在,它承載了任意數量的雙向數據流(Stream)。
消息(Message)是與邏輯請求或響應對應的完整的一系列幀。
幀(Frame)是最小的通訊單位,來自不一樣數據流的幀能夠交錯發送,而後再根據每一個幀頭的數據流標識符從新組裝。
2.服務端推送
HTTP/2.0 在客戶端請求一個資源時,會把相關的資源一塊兒發送給客戶端,客戶端就不須要再次發起請求了。例如客戶端請求 page.html 頁面,服務端就把 script.js 和 style.css 等與之相關的資源一塊兒發給客戶端。
3.首部壓縮
HTTP/1.1 的首部帶有大量信息,並且每次都要重複發送。
HTTP/2.0 要求客戶端和服務器同時維護和更新一個包含以前見過的首部字段表,從而避免了重複傳輸。
不只如此,HTTP/2.0 也使用 Huffman 編碼對首部字段進行壓縮。
HTTP 有如下安全性問題:
使用明文進行通訊,內容可能會被竊聽;
不驗證通訊方的身份,通訊方的身份有可能遭遇假裝;
沒法證實報文的完整性,報文有可能遭篡改。
加密:
1. 對稱密鑰加密
對稱密鑰加密(Symmetric-Key Encryption),加密和解密使用同一密鑰。
優勢:運算速度快;
缺點:沒法安全地將密鑰傳輸給通訊方。
2.非對稱密鑰加密
非對稱密鑰加密,又稱公開密鑰加密(Public-Key Encryption),加密和解密使用不一樣的密鑰。
公開密鑰全部人均可以得到,通訊發送方得到接收方的公開密鑰以後,就可使用公開密鑰進行加密,接收方收到通訊內容後使用私有密鑰解密。
非對稱密鑰除了用來加密,還能夠用來進行簽名。由於私有密鑰沒法被其餘人獲取,所以通訊發送方使用其私有密鑰進行簽名,通訊接收方使用發送方的公開密鑰對簽名進行解密,就能判斷這個簽名是否正確。
優勢:能夠更安全地將公開密鑰傳輸給通訊發送方;
缺點:運算速度慢。
真正使用時,其實是先用發送方獲得接收方非對稱密鑰加密的公開密鑰,而後用這個公開密鑰將其對稱密鑰進行加密,接受方收到後解密獲得對稱密鑰,以後就以這個對稱密鑰進行通訊。
認證
經過使用 證書 來對通訊方進行認證。
數字證書認證機構(CA,Certificate Authority)是客戶端與服務器雙方均可信賴的第三方機構。
服務器的運營人員向 CA 提出公開密鑰的申請,CA 在判明提出申請者的身份以後,會對已申請的公開密鑰作數字簽名,而後分配這個已簽名的公開密鑰,並將該公開密鑰放入公開密鑰證書後綁定在一塊兒。
進行 HTTPS 通訊時,服務器會把證書發送給客戶端。客戶端取得其中的公開密鑰以後,先使用數字簽名進行驗證,若是驗證經過,就能夠開始通訊了。
首先看爲何要認證?
細心的人可能已經注意到了若是使用非對稱加密算法,咱們的客戶端A,B須要一開始就持有公鑰,要不無法開展加密行爲啊。
這下,咱們又遇到新問題了,如何讓A、B客戶端安全地獲得公鑰?
client獲取公鑰最最直接的方法是服務器端server將公鑰發送給每個client用戶,但這個時候就出現了公鑰被劫持的問題,如上圖,client請求公鑰,在請求返回的過程當中被×××劫持,那麼咱們將採用劫持後的假祕鑰進行通訊,則後續的通信過程都是採用假祕鑰進行,數據庫的風險仍然存在。在獲取公鑰的過程當中,咱們又引出了一個新的話題:如何安全的獲取公鑰,並確保公鑰的獲取是安全的, 那就須要用到終極武器了:SSL 證書(須要購買)和CA機構
解決:
在第 ② 步時服務器發送了一個SSL證書給客戶端,SSL 證書中包含的具體內容有證書的頒發機構、有效期、公鑰、證書持有者、簽名,經過第三方的校驗保證了身份的合法,解決了公鑰獲取的安全性(中間人的公鑰沒有第三方的保證)
以瀏覽器爲例說明以下整個的校驗過程:
(1)首先瀏覽器讀取證書中的證書全部者、有效期等信息進行一一校驗
(2)瀏覽器開始查找操做系統中已內置的受信任的證書發佈機構CA,與服務器發來的證書中的頒發者CA比對,用於校驗證書是否爲合法機構頒發
(3)若是找不到,瀏覽器就會報錯,說明服務器發來的證書是不可信任的。
(4)若是找到,那麼瀏覽器就會從操做系統中取出 頒發者CA 的公鑰,而後對服務器發來的證書裏面的簽名進行解密
(5)瀏覽器使用相同的hash算法計算出服務器發來的證書的hash值,將這個計算的hash值與證書中籤名作對比
(6)對比結果一致,則證實服務器發來的證書合法,沒有被冒充
(7)此時瀏覽器就能夠讀取證書中的公鑰,用於後續加密了
圖裏的DATA至關於要發送的公鑰。
首先服務器向CA請求,CA對這個公鑰進行數字簽名,而後放入證書。
服務器將這個證書發送給對端,對端接收到後首先判斷CA是否是合法機構,若是是就拿出一個CA的公鑰,對簽名進行解密,解密獲得數字簽名。而後將數據(也就是公鑰)進行hash,將獲得的結果與數字簽名進行對比,若是相同則證實合法,就能夠用這個公鑰進行對稱密鑰的加密了。
完整性保護
SSL 提供報文摘要功能來進行完整性保護。
HTTP 也提供了 MD5 報文摘要功能,但不是安全的。例如報文內容被篡改以後,同時從新計算 MD5 的值,通訊接收方是沒法意識到發生了篡改。
HTTPS 的報文摘要功能之因此安全,是由於它結合了加密和認證這兩個操做。試想一下,加密以後的報文,遭到篡改以後,也很難從新計算報文摘要,由於沒法輕易獲取明文。
strcpy與memcpy:
區別:
strcpy與memcpy不一樣存在於:strcpy只複製字符串,而memcpy能夠複製任何內容(字符數組、結構體、類)等。strcpy不須要指定長度由結束符」\0」而結束戰鬥的,memcpy由第三個參數所決定
strcpy:
功能:char *strcpy(char *dest,char *src);
把src所指由NULL結束的字符串複製到dest所指的數組中
src和dest所指內存區域不能夠重疊且dest必須有足夠的空間來容納src的字符串。
問題:
strcpy只是複製字符串,但不限制複製的數量。很容易形成緩衝溢出,也就是說,不過dest有沒有足夠的空間來容納src的字符串,它都會把src指向的字符串所有複製到從dest開始的內存
內存重疊的問題:
char s[10]="hello";
strcpy(s, s+1); //應返回ello,
//strcpy(s+1, s); //應返回hhello,但實際會報錯,由於dst與src重疊了,把'\0'覆蓋了
當dst在src和src的結尾之間時會出現問題:
好比hello,dst=ello,src=hello:先把h賦值到e,總體變爲hhllo,如今src爲hllo,dst爲llo;再複製一次,變爲hhhllo....持續到dst指向'\0'而如今src指向的是h,而後此次複製至關於把字符串的\0覆蓋了,也就是說這個複製永遠不會停下來。最後變爲hhhhhhhhhhhhhhhhhhhhh直到複製到不可訪問的內存
這是因爲src後面準備複製到dst中的數據被dst所覆蓋的緣由
不考慮內存重疊的stcpy的實現:
char * strcpy(char *dst,const char *src) //[1] { assert(dst != NULL && src != NULL); //[2] char *ret = dst; //[3] while ((*dst++=*src++)!='\0'); //[4] return ret; }
也就是說,若是dst位於src~src+strlen(src)+1之間(+1是'\0'),則因爲會覆蓋'\0'而致使沒法中止
所以對於這時,要從後往前拷貝,複製前統計長度,用長度來控制中止。
char * strcpy(char *dst,const char *src) { assert(dst != NULL && src != NULL); int cnt=strlen(src)+1; char *ret = dst; if (dst >= src && dst <= src+cnt-1) //內存重疊,從高地址開始複製 { dst = dst+cnt-1; src = src+cnt-1; while (cnt--) *dst-- = *src--; } else //正常狀況,從低地址開始複製 { while (cnt--) *dst++ = *src++; } return ret; }
memcpy:
void *memcpy(void *destin, void *source, unsigned n);
功能:
將src逐字節拷貝到dst,拷貝n個字節
memcpy不會添加'\0',只是機械地拷貝,由n來決定中止。
問題:
一樣,當出現內存重疊時,如dst在src和src+strlen(stc)+1中間時:src後面尚未複製到dst中的數據會被覆蓋掉
解決辦法也是從高地址往低地址拷貝
void *memcpy(void *dest, const void *src, size_t count) { char *d; const char *s; if (dest > (src+size)) || (dest < src)) { d = dest; s = src; while (count--) *d++ = *s++; } else /* overlap */ { d = (char *)(dest + count - 1); /* offset of pointer is from 0 */ s = (char *)(src + count -1); while (count --) *d-- = *s--; } return dest;
memmove:
void *memmove( void* dest, const void* src, size_t count );
功能:
memmove用於拷貝字節,若是目標區域和源區域有重疊的話,memmove可以保證源串在被覆蓋以前將重疊區域的字節拷貝到目標區域中,但複製後源內容會被更改。可是當目標區域與源區域沒有重疊則和memcpy函數功能相同。
void* memmove(void* dest, const void* src, size_t n) { char * d = (char*) dest; const char * s = (const char*) src; if (s>d) { // start at beginning of s while (n--) *d++ = *s++; } else if (s<d) { // start at end of s d = d+n-1; s = s+n-1; while (n--) *d-- = *s--; } return dest; }
memset:
功能:
void *memSet(void *s, int c, size_t n) { if (NULL == s || n < 0) return NULL; char * tmpS = (char *)s; while(n-- > 0) *tmpS++ = c; return s; }
strncpy:
strncpy(char *to,const char *from,int size);
char *strncpy(char *dst, const char *src, size_t len) { assert(des != NULL && src != NULL); int offset = 0; char *ret = dst; char *tmp; if(strlen(src) < len) { offset = len - strlen(src);//補齊0的個數 len = strlen(src);//複製的個數 } if(dst >= src && dst <= src + len -1) { dst = dst + len - 1; src = src + len - 1; tmp = dst;//記錄補0的位置 while(len--) *dst-- = *src--; } else { while(len++) *dst++ = *src++; tmp = dst; } while(offset--) *tmp++ = '\0';//補0 return ret; }
strlcpy:
size_t strlcpy(char *dest, const char *src, size_t size)
實現:
size_t Test_strlcpy(char *dest, const char *src, size_t size) { size_t ret = strlen(src); if (size) { //這句判斷大讚,起碼有效防止源字符串的越界問題 size_t len = (ret >= size) ? size - 1 : ret;//防止緩衝區溢出,若是不夠則只複製到緩衝區滿.ret等於size也要考慮到size-1裏,由於'\0'是後面統一加的 memcpy(dest, src, len); dest[len] = '\0'; } return ret; }
1.開放定址法:
Hi=(H(key)+di)Mod m;m爲哈希表長度
分爲三種:
1).線性探測再散列:
di={1,2,3,4,5,...m-1}
2).二次探測再散列:
di={1,-1,4,-4,9,-9....,k^2,-k^2},k<=m/2;
3).僞隨機探測再序列
2.再哈希
Hi=RHi(key) i=1,2,3,4...k
換其餘的哈希方法來計算地址,直到能夠生成不衝突的哈希
3.開鏈法:
鏈表
當bucket中有3/4的有負載時,要進行rehash,重建一個2倍大小的bucket數組,因爲重建後hash長度變了,因此要從新對其中的值進行哈希並開鏈。
當鏈表長度超過8,要將鏈表轉爲紅黑樹。
爲何臨界值是8?
理想狀況下隨機hashCode算法下全部bin中節點的分佈頻率會遵循泊松分佈,咱們能夠看到,一個bin中鏈表長度達到8個元素的機率爲0.00000006,幾乎是不可能事件
並且n=8時,8與log28=3差距不算太大,綜合考慮建樹產生的消耗。
CPU緩存(Cache Memory)位於CPU與內存之間的臨時存儲器,它的容量比內存小但交換速度快。在緩存中的數據是內存中的一小部分,但這一小部分是短期內CPU即將訪問的,當CPU調用大量數據時,就可避開內存直接從緩存中調用,從而加快讀取速度。因而可知,在CPU中加入緩存是一種高效的解決方案,這樣整個內存儲器(緩存+內存)就變成了既有緩存的高速度,又有內存的大容量的存儲系統了。緩存對CPU的性能影響很大,主要是由於CPU的數據交換順序和CPU與緩存間的帶寬引發的。
緩存的工做原理是當CPU要讀取一個數據時,首先從緩存中查找,若是找到就當即讀取並送給CPU處理;若是沒有找到,就用相對慢的速度從內存中讀取並送給CPU處理,同時把這個數據所在的數據塊調入緩存中,可使得之後對整塊數據的讀取都從緩存中進行,沒必要再調用內存。
正是這樣的讀取機制使CPU讀取緩存的命中率很是高(大多數CPU可達90%左右),也就是說CPU下一次要讀取的數據90%都在緩存中,只有大約10%須要從內存讀取。這大大節省了CPU直接讀取內存的時間,也使CPU讀取數據時基本無需等待。總的來講,CPU讀取數據的順序是先緩存後內存。
time的頭文件爲time.h
rand和srand頭文件爲stdlib.h
好比5 200 9 12 33 44
第一次隨機選中200,將200與44交換。
下一次就只在0~4間選取
在下一次就只在0~3件選取
第一次機率爲1/n
第二次的機率爲,第一次沒選中這個數的機率,也就是(n-1)/n;乘上第二次被選中的機率,也就是1/(n-1),爲1/n
第三次爲(n-1)/n*(n-2)/(n-1)*1/(n-2)=1/n
....
void shuffle(vector<int> arr,int k) { int count=0; for(int i=0;i<k;i++) { srand(time(NULL)); int idx=rand()%(arr.size()-count); swap(arr[idx],arr[arr.size()-1-count]); count++; } }
http://www.javashuo.com/article/p-omqouzes-p.html
轉換爲逆波蘭表達式:
'('與')'有最高的優先級;*,/有次級的優先級;+,-最低優先級
遇到字母直接輸出;
遇到的運算符,若是比棧頂的運算符優先級低,且棧頂不爲(,則輸出棧頂內的元素直到棧頂的優先級小於當前運算符,而後將運算符push進棧:由於當前的運算符優先級低,因此數字應該優先與優先級高的向結合。
遇到')',則輸出棧內元素直到棧頂爲(,再將(pop掉。
最後,若是棧不爲空,則將棧內元素依次輸出
class Solution { public: string Trans(string in) { stack<char> aux; int p1=0; string ans; while(p1<in.size()) { if(isNum(in[p1]))//a+b*c+(d*e+f)*g ans+=in[p1]; else { if(aux.empty()) aux.push(in[p1]); else if(in[p1]==')')//遇到),pop到棧頂爲( { while(aux.top()!='(') { ans+=aux.top(); aux.pop(); } aux.pop(); } else { while(!aux.empty()&&Jugde(aux.top())>=Jugde(in[p1])&&aux.top()!='(')//只有當前運算符優先級比棧頂低,且棧頂不爲( { ans+=aux.top(); aux.pop(); } aux.push(in[p1]); } } p1++; } while(!aux.empty()) { ans+=aux.top(); aux.pop(); } return ans; } bool isNum(char str)//判斷是不是字母 { if(str!='('&&str!=')'&&str!='+'&&str!='-' &&str!='*'&&str!='/') return true; else return false; } int Jugde(char str)//評判優先級 { if(str=='('||str==')') return 2; else if(str=='*'||str=='/') return 1; else return 0; } };
PC1準備向PC2發送數據包
(1)PC1檢查報文的目的IP地址,發現和本身不在同一網段,則須要進行三層轉發,經過網關轉發報文信息;
(2)PC1檢查本身的ARP表,發現網關的MAC地址不在本身的ARP表裏;
(3)PC1——>Router(網關)發出ARP請求報文;
(4)Router將PC1的MAC地址學習到本身的ARP表
(5)Router(網關)——>PC1發出ARP應答報文;
(6) PC1學習到Router(網關)的mac地址,發出報文,此時源ip、目的ip不變,目的mac爲Router(網關)的mac;
(7)PC1——> Router(網關)發出報文;
(8)Router(網關)收到報文,發現是三層報文(緣由是報文的目的mac是本身的mac);
(9)Router(網關)檢查本身的路由表(FIB),發現目的ip在本身的直連網段;
(10)Router檢查本身的arp表,若是發現有與目的ip對應的mac地址則直接封裝報文(目的ip、源ip不變,目的mac爲查arp表所得mac)發送給PC2;
(11)若是查ARP表沒有獲得與目的ip對應MAC,則重複(3)發arp請求;
(12)PC2收到ARP廣播報文,發現目的IP是本身的IP,因而給Router發送ARP應答報文。報文中會附上本身的mac地址;
(13)Router收到應答報文後,目的mac改成PC2的mac,而後向PC2發送數據幀;
(14)PC2向Router發送報文;
第1、100000名實時遍歷系統必定承受不了或者說這樣作代價太大,那麼能夠首先遍歷一遍,挑選出戰鬥力最高的1000名,而後後面只遍歷這1000名就能夠了,由於前500名大機率都是前一千名產生的,減小系統開銷。
第2、爲了防止某些玩家充錢了,大幅提高戰鬥力,那麼能夠設置一個閾值,若是某個玩家戰鬥力增長速度超過閾值,那麼這個玩家也應該歸入實時排序過程當中。
第3、最後100000名玩家的戰鬥力能夠按期在服務器壓力不大的時候,好比休服時期或者夜間,作總體排序,以便校驗數據的準確性。
1.指針參數傳遞本質上是值傳遞,它所傳遞的是一個地址值。值傳遞的特色是,被調函數對形式參數的任何操做都是做爲局部變量進行的,不會影響主調函數的實參變量的值(形參指針變了,實參指針不會變)。
2.引用參數傳遞過程當中,被調函數的形式參數也做爲局部變量在棧中開闢了內存空間,可是這時存放的是由主調函數放進來的實參變量的地址.所以,被調函數對形參的任何操做都會影響主調函數中的實參變量。
3.從編譯的角度來說,程序在編譯時分別將指針和引用添加到符號表上,符號表中記錄的是變量名及變量所對應地址。指針變量在符號表上對應的地址值爲指針變量的地址值,而引用在符號表上對應的地址值爲引用對象的地址值(與實參名字不一樣,地址相同)。符號表生成以後就不會再改,所以指針能夠改變其指向的對象(指針變量中的值能夠改),而引用對象則不能修改。
1. 申請的內存所在位置
new操做符從自由存儲區(free store)上爲對象動態分配內存空間,而malloc函數從堆上動態分配內存
new在靜態存儲區上分配內存
2.返回類型安全性
new操做符內存分配成功時,返回的是對象類型的指針,類型嚴格與對象匹配,無須進行類型轉換,故new是符合類型安全性的操做符。而malloc內存分配成功則是返回void * ,須要經過強制類型轉換將void*指針轉換成咱們須要的類型。
3.內存分配失敗時的返回值
new內存分配失敗時,會拋出bac_alloc異常,它不會返回NULL;malloc分配內存失敗時返回NULL。
new的檢查:
1.try後經過catch捕捉異常
try{
int* p = new int[SIZE];
//其餘代碼
}catch( const bad_alloc& e ){
return -1;
}
2.抑制new拋出異常,而返回空指針
int* p = new (std::nothrow) int; //這樣,若是new失敗了,就不會拋出異常,而是返回空指針
if( p==0 )//如此這般,這個判斷就有意義了
return -1;
4.是否須要指定內存大小
使用new操做符申請內存分配時無須指定內存塊的大小,編譯器會根據類型信息自行計算,而malloc則須要顯式地指出所需內存的尺寸
5.是否調用構造函數/析構函數
使用new操做符來分配對象內存時會經歷三個步驟:
第一步:調用operator new 函數(對於數組是operator new[])分配一塊足夠大的,原始的,未命名的內存空間以便存儲特定類型的對象。
第二步:編譯器運行相應的構造函數以構造對象,併爲其傳入初值。
第三部:對象構造完成後,返回一個指向該對象的指針。
使用delete操做符來釋放對象內存時會經歷兩個步驟:
第一步:調用對象的析構函數。
第二步:編譯器調用operator delete(或operator delete[])函數釋放內存空間
5.是否能夠被重載
opeartor new /operator delete能夠被重載。
6.可以直觀地從新分配內存
使用malloc分配的內存後,若是在使用過程當中發現內存不足,可使用realloc函數進行內存從新分配實現內存的擴充。realloc先判斷當前的指針所指內存是否有足夠的連續空間,若是有,原地擴大可分配的內存地址,而且返回原來的地址指針;若是空間不夠,先按照新指定的大小分配空間,將原有數據從頭至尾拷貝到新分配的內存區域,然後釋放原來的內存區域。new沒有這樣直觀的配套設施來擴充內存
合法的new/delete和mallc/free示意圖
new [] 時多分配 4 個字節,用於存儲用戶實際分配的對象個數。而new不會多分配。
mallc和free的實際大小,這個信息存儲在內存塊的塊頭裏面。其中最重要的就是指示實際分配的內存大小(單位:字節),那麼在free時,就要將用戶的傳入的地址,減去塊頭長度找到實際分配內存的起始地址而後釋放掉。塊頭的長度是8字節。
首先new不會偏移四個字節,所以new返回的地址實際上就是對象的地址。而當使用delete[]時,會首先向前(低地址)方向移動四個字節,這四個字節記錄了分配對象的個數。而如今因爲使用的是new,也就是說其實是不存在這四個字節的,所以實際上delete[]向前移動四個字節,這個地址落在了塊頭裏,而後按塊頭裏後四個字節的值進行析構,而這是一個比較偏大的值,最終致使內存越界。
new時不會偏移4字節,delete[]時,編譯器就是經過標識符[]而執行減4字節操做。從上圖可知,減後的地址值會落到塊頭中(塊頭有8個字節),同時編譯器從塊頭中提取4字節中的值做爲本身執行析構對象的個數,而這4個字節是實際內存的長度,這是一個比較偏大的值,好比256,而後編譯器對隨後的內存對象執行析構,基本上都會致使內存越界,這必然失敗。
也就是說,將new裏塊頭的前四個字節當作了析構的個數,最終致使內存越界
new[],若是是內嵌類型,好比char或者int等等,就是C數組,那麼它不會向後(地址變大的方向)偏移4個字節,也就是說他沒有自定義類型的記錄對象個數的4個字節(多是由於trivial的緣由?),所以執行delete時,顯然不會出現任何問題(由於實際上對於基本數據類型來講,new[]等價於new)。
可是若是是自定義類型呢?那麼new[]時就會向後偏移4個字節(由於有4個字節用於存放對象個數,而實際上第一個對象的地址在malloc分配的內存的四個字節以後),從malloc的返回地址偏移4個字節用來存儲對象個數,若是使用delete,編譯器是識別不出釋放的是數組,那麼它會直接將傳入對象的首地址值處執行一次對象析構,這個時候還不會出現問題(由於地址值就是第一個對象的位置,也就是malloc返回的地址的後面四個字節的位置),可是再進一步,它把對象的首地址值傳遞給free時,那麼這個地址值並非malloc返回的地址,而是相差了4個字節,此時free向前偏移取出malloc的實際長度時,就會取出對象的個數值做爲實際的分配長度進行釋放(也就是說,free以當前位置爲起點,向前找8個字節,由於這8個字節是塊頭信息,而因爲其實是用new[]分配的,所以實際上向前移動八個字節並無移動到頭,而是移動到了塊頭的後四個字節,這後四個字節與存儲對象個數的4個字節一塊兒組合在一塊兒),顯然這將致使只釋放了n字節,其他的塊頭一部分和除n字節的全部內存都泄露了(由於實際上只釋放了第一個對象以及第一個對象前面8個字節,這8個字節由記錄對象個數的4個字節和塊頭的後4個字節組成),而且只有第一個對象成功析構,其他都沒有析構操做。通常對象個數n是個很是小的值,好比128個對象,那麼free只釋放了128字節。(注意:不一樣的libc實現不一樣,這裏只示例闡述原理,不深究數字)
總而言之:
對於使用delete[]來釋放new時:因爲錯誤地認爲有4個字節存放對象個數,而致使偏移到塊頭中,失敗的緣由是析構對象個數錯誤(過多)而致使內存越界。
對於使用delete來釋放new[]時:第一個對象的析構是不會出問題的,問題出在free,因爲使用delete所以認爲不會有4個字節存放對象個數,所以向前偏移8個字節而找到了塊頭的後四個字節的位置,今後處開始釋放,所以會致使內存泄漏(至少塊頭的前4個字節是泄漏的),且大量對象沒有析構(除了第一個)。
當進程執行過程當中發生缺頁中斷時,須要進行頁面換入,步驟以下:
<1> 首先硬件會陷入內核,在堆棧中保存程序計數器。大多數機器將當前指令的各類狀態信息保存在CPU中特殊的寄存器中。
<2>啓動一個彙編代碼例程保存通用寄存器及其它易失性信息,以避免被操做系統破壞。這個例程將操做系統做爲一個函數來調用。
(在頁面換入換出的過程當中可能會發生上下文換行,致使破壞當前程序計數器及通用寄存器中本進程的信息)
<3>當操做系統發現是一個頁面中斷時,查找出來發生頁面中斷的虛擬頁面(進程地址空間中的頁面)。這個虛擬頁面的信息一般會保存在一個硬件寄存器中,若是沒有的話,操做系統必須檢索程序計數器,取出這條指令,用軟件分析該指令,經過分析找出發生頁面中斷的虛擬頁面。
<4>檢查虛擬地址的有效性及安全保護位。若是發生保護錯誤,則殺死該進程(看看訪問的頁面是否合法,如使用NULL致使被殺死)。
<5>操做系統查找一個空閒的頁框(物理內存中的頁面),若是沒有空閒頁框則須要經過頁面置換算法找到一個須要換出的頁框。
<6>若是找的頁框中的內容被修改了,則須要將修改的內容保存到磁盤上,此時會引發一個寫磁盤調用,發生上下文切換(在等待磁盤寫的過程當中讓其它進程運行)。
(注:此時須要將頁框置爲忙狀態,以防頁框被其它進程搶佔掉)
<7>頁框乾淨後,操做系統根據虛擬地址對應磁盤上的位置,將保持在磁盤上的頁面內容複製到「乾淨」的頁框中,此時會引發一個讀磁盤調用,發生上下文切換。
<8>當磁盤中的頁面內容所有裝入頁框後,向操做系統發送一箇中斷。操做系統更新內存中的頁表項,將虛擬頁面映射的頁框號更新爲寫入的頁框,並將頁框標記爲正常狀態。
<9>恢復缺頁中斷髮生前的狀態,將程序指令器從新指向引發缺頁中斷的指令。
<10>調度引發頁面中斷的進程,操做系統返回彙編代碼例程。
<11>彙編代碼例程恢復現場,將以前保存在通用寄存器中的信息恢復。
在段式存儲管理中,將程序的地址空間劃分爲若干段(segment),如代碼段,數據段,堆棧段;這樣每一個進程有一個二維地址空間,相互獨立,互不干擾。段式管理的優勢是:沒有內碎片(由於段大小可變,改變段大小來消除內碎片)。但段換入換出時,會產生外碎片(好比4k的段換5k的段,會產生1k的外碎片)
在頁式存儲管理中,將程序的邏輯地址劃分爲固定大小的頁(page),而物理內存劃分爲一樣大小的頁框,程序加載時,能夠將任意一頁放入內存中任意一個頁框,這些頁框沒必要連續,從而實現了離散分離。頁式存儲管理的優勢是:沒有外碎片(由於頁的大小固定),但會產生內碎片(一個頁可能填充不滿)
(1) 分頁僅僅是因爲系統管理的須要而不是用戶的須要。段則是信息的邏輯單位,它含有一組其意義相對完整的信息。分段的目的是爲了能更好地知足用戶的須要。
(2) 頁的大小固定且由系統決定,由系統把邏輯地址劃分爲頁號和頁內地址兩部分,是由機器硬件實現的,於是在系統中只能有一種大小的頁面;而段的長度卻不固定,決定於用戶所編寫的程序,一般由編譯程序在對源程序進行編譯時,根據信息的性質來劃分。
(3) 分頁的做業地址空間是一維的,即單一的線性地址空間,程序員只需利用一個記憶符,便可表示一個地址;而分段的做業地址空間則是二維的,程序員在標識一個地址時,既需給出段名,又需給出段內地址。
1.默認的繼承訪問權。class默認的是private,strcut默認的是public。
2.默認訪問權限:struct做爲數據結構的實現體,它默認的數據訪問控制是public的,而class做爲對象的實現體,它默認的成員變量訪問控制是private的
3.「class」這個關鍵字還用於定義模板參數,就像「typename」。但關鍵字「struct」不用於定義模板參數
extern 做用1:聲明外部變量
extern的原理很簡單,就是告訴編譯器:「你如今編譯的文件中,有一個標識符雖然沒有在本文件中定義,可是它是在別的文件中定義的全局變量,你要放行!」
extern 做用2:在C++文件中調用C方式編譯的函數
extern "C"
的做用是讓 C++ 編譯器將 extern "C"
聲明的代碼看成 C 語言代碼處理,能夠避免 C++ 因符號修飾致使代碼不能和C語言庫中的符號進行連接的問題。