static的做用html
static修飾變量只能在本範圍內可見(由external變爲internal,做用域和連接屬性並無改變):修飾全局變量只能在本cpp文件中可見,修飾局部變量只能在該代碼塊內可見。修飾類的靜態成員在類的對象中共享這一份數據。ios
其實就是一個類,當銷燬指向的內存時,能夠不用手動free內存,它會自動釋放內存空間。c++
auto_ptr面試
unique_ptr數據庫
shared_ptrwindows
shared_ptr實現共享式擁有概念。多個智能指針能夠指向相同對象,該對象和其相關資源會在「最後一個引用被銷燬」時候釋放。從名字share就能夠看出了資源能夠被多個指針共享,它使用計數機制來代表資源被幾個指針共享。數組
weak_ptr服務器
weak_ptr是用來解決shared_ptr相互引用時的死鎖問題,若是說兩個shared_ptr相互引用,那麼這兩個指針的引用計數永遠不可能降低爲0,資源永遠不會釋放。它是對對象的一種弱引用,不會增長對象的引用計數,和shared_ptr之間能夠相互轉化,shared_ptr能夠直接賦值給它,它能夠經過調用lock函數來得到shared_ptr。多線程
智能指針主要用於管理在堆上分配的內存,它將普通的指針封裝爲一個棧對象。當棧對象的生存週期結束後,會在析構函數中釋放掉申請的內存,從而防止內存泄漏。閉包
C++ 的全局對象的構造函數會在 main 函數以前先運行,其實在 c 語言裏面很早就有啦,在 gcc 中可使用 attribute 關鍵字指定以下(在編譯器編譯的時候就絕決定了)
它們的底層都是用紅黑樹實現的,關於紅黑樹和avl樹的區別(首先紅黑樹是不符合AVL樹的平衡條件的,即每一個節點的左子樹和右子樹的高度最多差1的二叉查找樹。可是提出了爲節點增長顏色,紅黑是用非嚴格的平衡來換取增刪節點時候旋轉次數的下降,任何不平衡都會在三次旋轉以內解決,而AVL是嚴格平衡樹,所以在增長或者刪除節點的時候,根據不一樣狀況,旋轉的次數比紅黑樹要多。因此紅黑樹的插入效率更高!!!
extern void *memset(void *buffer, int c, int count) ;
+ buffer:指針或者數組 + c:賦給buffer的值 + count:buffer的長度
通常用來給一段內存空間所有設置爲某個字符。
管道、系統IPC(信號、信號量、共享內存、消息隊列)、套接字socket
進程和線程共享地址空間,進程的堆共享給線程,可是每一個線程有本身獨立的棧
互斥量、信號量、臨界區
每一個進程的地址空間是獨立的,位於一個進程的普通內存區域中的對象是沒法被其它進程所訪問的,能知足這一要求的內存區域是共享內存,於是同步對象要在進程的共享內存區域內建立。同步對象還能夠放在文件中。同步對象能夠比建立它的進程具備更長的生命週期。
1. std::lock_guard 2. std::unique_lock 3. std::condition_variable
#include <iostream> #include <string> #include <thread> #include <vector> #include <mutex> using std::thread; using std::vector; using std::cout; using std::endl; using std::mutex; class Incrementer { private: int counter; mutex m; public: Incrementer() : counter{0} { }; void operator()() { for(int i = 0; i < 100000; i++) { this->m.lock(); this->counter++; this->m.unlock(); } } int getCounter() const { return this->counter; } }; int main() { // Create the threads which will each do some counting vector<thread> threads; Incrementer counter; threads.push_back(thread(std::ref(counter))); threads.push_back(thread(std::ref(counter))); threads.push_back(thread(std::ref(counter))); for(auto &t : threads) { t.join(); } cout << counter.getCounter() << endl; return 0; }
運行結果
修改其中代碼
for(int i = 0; i < 100000; i++) { this->m.lock(); try { this->counter++; this->m.unlock(); } catch(...) { this->m.unlock(); throw; } }
#include <iostream> #include <mutex> #include <thread> #include <condition_variable> std::mutex g_mutex; // 用到的全局鎖 std::condition_variable g_cond; // 用到的條件變量 int g_i = 0; bool g_running = true; void ThreadFunc(int n) { // 線程執行函數 for (int i = 0; i < n; ++i) { { std::lock_guard<std::mutex> lock(g_mutex); // 加鎖,離開{}做用域後鎖釋放 ++g_i; std::cout << "plus g_i by func thread " << std::this_thread::get_id() << std::endl; } } std::unique_lock<std::mutex> lock(g_mutex); // 加鎖 while (g_running) { std::cout << "wait for exit" << std::endl; g_cond.wait(lock); // wait調用後,會先釋放鎖,以後進入等待狀態;當其它進程調用通知激活後,會再次加鎖 } std::cout << "func thread exit" << std::endl; } int main() { int n = 100; std::thread t1(ThreadFunc, n); // 建立t1線程(func thread),t1會執行`ThreadFunc`中的指令 for (int i = 0; i < n; ++i) { { std::lock_guard<std::mutex> lock(g_mutex); ++g_i; std::cout << "plus g_i by main thread " << std::this_thread::get_id() << std::endl; } } { std::lock_guard<std::mutex> lock(g_mutex); g_running = false; g_cond.notify_one(); // 通知其它線程 } t1.join(); // 等待線程t1結束 std::cout << "g_i = " << g_i << std::endl; }
首先,這在一個局部做用域內, std::lock_guard 在構造時,會調用 g_mutex->lock() 方法;
局部做用域代碼結束後, std:;lock_guard 的析構函數會被調用,函數中會調用 g_mutex->unlock() 方法。
當線程調用 g_cond.wait(lock) 前要先手動調用 lock->lock() ,這裏是經過 std::unique_lock 的構造方法實現的;
當線程調用 g_cond.wait(lock) 進入等待後,會調用 lock->unlock() 方法,因此這也是前面構造lock時使用了 std::unique_lock ;
通知使用的 g_cond.notify_one() ,這個能夠通知一個線程,另外還有 g_cond.notify_all() 用於通知全部線程;
線程收到通知的代碼放在一個while循環中,這是爲了防止APUE中提到的虛假通知。
從 實現原理上來說,Mutex屬於sleep-waiting類型的鎖。例如在一個雙核的機器上有兩個線程(線程A和線程B),它們分別運行在Core0和 Core1上。假設線程A想要經過pthread_mutex_lock操做去獲得一個臨界區的鎖,而此時這個鎖正被線程B所持有,那麼線程A就會被阻塞 (blocking),Core0 會在此時進行上下文切換(Context Switch)將線程A置於等待隊列中,此時Core0就能夠運行其餘的任務(例如另外一個線程C)而沒必要進行忙等待。而Spin lock則否則,它屬於busy-waiting類型的鎖,若是線程A是使用pthread_spin_lock操做去請求鎖,那麼線程A就會一直在 Core0上進行忙等待並不停的進行鎖請求,直到獲得這個鎖爲止。因此,自旋鎖通常用用多核的服務器。
int num = 0; spin_mutex sm; void thread_proc() { for(int i = 0; i < 100000; ++i) { sm.lock(); ++num; sm.unlock(); } } int main() { std::thread td1(thread_proc), td2(thread_proc); td1.join(); td2.join(); std::cout << num << std::endl; return 0; }
windows系統中臨界區(Critical Section)、事件對象(Event)
nullptr
類型推導 auto(不能用於推導數組類型,不能用於函數傳參) 和decltype關鍵字
有的時候咱們只須要計算表達式得出的類型,不須要返回值
auto x = 1; auto y = 2; decltype(x+y) z;
拖尾返回類型、auto 與 decltype 配合,利用 auto 關鍵字將返回類型後置:
template<typename T, typename U> auto add(T x, U y) -> decltype(x+y) { return x+y; }
初始化列表
struct A { int a; float b; }; struct B { B(int _a, float _b): a(_a), b(_b) {} private: int a; float b; }; A a {1, 1.1}; // 統一的初始化語法 B b {2, 2.2};
Lambda表達式
提供了一個相似匿名函數的特性,而匿名函數則是在須要一個函數,可是又不想費力去命名一個函數的狀況下去使用的。
[ caputrue ] ( params ) opt -> ret { body; };
捕獲列表:lambda表達式的捕獲列表精細控制了lambda表達式可以訪問的外部變量,以及如何訪問這些變量。
int a = 0; auto f = [=] { return a; }; a+=1; cout << f() << endl; //輸出0 int a = 0; auto f = [&a] { return a; }; a+=1; cout << f() <<endl; //輸出1
class A { public: int i_ = 0; void func(int x,int y){ auto x1 = [] { return i_; }; //error,沒有捕獲外部變量 auto x2 = [=] { return i_ + x + y; }; //OK auto x3 = [&] { return i_ + x + y; }; //OK auto x4 = [this] { return i_; }; //OK auto x5 = [this] { return i_ + x + y; }; //error,沒有捕獲x,y auto x6 = [this, x, y] { return i_ + x + y; }; //OK auto x7 = [this] { return i_++; }; //OK }; int a=0 , b=1; auto f1 = [] { return a; }; //error,沒有捕獲外部變量 auto f2 = [&] { return a++ }; //OK auto f3 = [=] { return a; }; //OK auto f4 = [=] {return a++; }; //error,a是以複製方式捕獲的,沒法修改 auto f5 = [a] { return a+b; }; //error,沒有捕獲變量b auto f6 = [a, &b] { return a + (b++); }; //OK auto f7 = [=, &b] { return a + (b++); }; //OK
lambda表達式的大體原理:每當你定義一個lambda表達式後,編譯器會自動生成一個匿名類(這個類重載了()運算符),咱們稱爲閉包類型(closure type)。那麼在運行時,這個lambda表達式就會返回一個匿名的閉包實例,是一個右值。因此,咱們上面的lambda表達式的結果就是一個個閉包。對於複製傳值捕捉方式,類中會相應添加對應類型的非靜態數據成員。在運行時,會用複製的值初始化這些成員變量,從而生成閉包。對於引用捕獲方式,不管是否標記mutable,均可以在lambda表達式中修改捕獲的值。至於閉包類中是否有對應成員,C++標準中給出的答案是:不清楚的,與具體實現有關。
#include<iostream> using namespace std; class coord { int x, y; public: coord(int i = 0, int j = 0) { x = i; y = j; } friend ostream& operator<<(ostream &stream, coord &ob);//這裏第二個參數採用了引用(&ob), //是爲了減小調用的開銷,使用引用參數只需把對象的地址傳進來就能夠了,而不需把每一個域份量逐一傳進來 //而消耗內存和時間。因此不用普通的對象作參數,雖然結果同樣。可是<<重載的函數返回值和第一個參數必須爲輸出流類ostream的的引用。 friend istream& operator>>(istream &input, coord &ob);//這裏的第二個參數必須爲引用,目的是函數體對參數a的修改能影響實參,由於從輸入 //流輸入的值要存入與a對應的實參中。注意重載輸出<<時的做用並非爲了修改實參,此點不一樣。 }; ostream & operator<<(ostream &stream, coord &ob) { stream << ob.x << "," << ob.y << endl;//stream爲ostream類的一個對象的引用,做爲左操做數(cout也是同樣,是C++中的兩個流對象) return stream; } istream& operator>>(istream &input, coord &ob) { cout << "Enter x and y value:"; input >> ob.x; input >> ob.y; return input; } int main() { coord a(55, 66), b(100, 220); cout << a << b; cin >> a; cin >> b; cout << a << b; return 0; }
分析:上面輸出重載函數的形參stream是ostream類對象的引用,返回值也是ostream類對象的引用。在main中cout<<a;cout是ostream類對象,a是coord類對象,因此能夠把其理解爲operator<<(cout,a);
#include <iostream> using namespace std; class Distance { private: int feet; // 0 到無窮 int inches; // 0 到 12 public: // 所需的構造函數 Distance(){ feet = 0; inches = 0; } Distance(int f, int i){ feet = f; inches = i; } ostream& operator<<( ostream & os) { os<<"英寸:"<<feet<<"\n英尺:"<<inches; return os; } }; int main () { Distance d1(20,18); d1<<cout;//至關於d1.operator<<(cout) }
容器
vector向量:相似數組操做。
size 是當前 vector 容器真實佔用的大小,也就是容器當前擁有多少個容器。
capacity 是指在發生 realloc 前能容許的最大元素數,即預分配的內存空間。
固然,這兩個屬性分別對應兩個方法:resize() 和 reserve()。
使用 resize() 容器內的對象內存空間是真正存在的。
使用 reserve() 僅僅只是修改了 capacity 的值,容器內的對象並無真實的內存空間(空間是"野"的)。
#include <iostream> #include <vector> using std::vector; int main(void) { vector<int> v; std::cout<<"v.size() == " << v.size() << " v.capacity() = " << v.capacity() << std::endl; v.reserve(10); std::cout<<"v.size() == " << v.size() << " v.capacity() = " << v.capacity() << std::endl; v.resize(10); v.push_back(0); std::cout<<"v.size() == " << v.size() << " v.capacity() = " << v.capacity() << std::endl; return 0; }
針對 capacity 這個屬性,STL 中的其餘容器,如 list map set deque,因爲這些容器的內存是散列分佈的,所以不會發生相似 realloc() 的調用狀況,所以咱們能夠認爲 capacity 屬性針對這些容器是沒有意義的,所以設計時這些容器沒有該屬性。
在 STL 中,擁有 capacity 屬性的容器只有 vector 和 string。
+ 阻塞IO(Blocking IO)
在這個例子中,咱們會經過UDP而不是TCP來舉例,由於對於UDP來講,等待數據就緒這一步更加直觀:要不就是收到了一個數據報,要不就是沒收到一個數據報.可是對於TCP來講,還有不少額外的變量.
上圖中的recvfrom是一個系統調用.當咱們執行一次系統調用的時候,有一次從用戶態到內核態的切換.
從上圖中咱們能夠看到,進程調用recvfrom以後,這個系統調用並不會當即返回,它會等到數據報到達而且被拷貝到應用程序的緩衝區中,或者出現了一個錯誤,纔會返回.咱們稱這個過程是阻塞的,應用程序只有在數據報被放入緩衝區以後,才能繼續進行.
非阻塞IO(Nonblocking IO)
非阻塞IO和阻塞IO相對,它會告訴內核,"當我要你完成的IO操做不能完成時,不要讓進程阻塞,你給我返回一個錯誤就好了".過程以下圖所示:
在上面的三個recvfrom操做中,因爲數據並無就緒,因此內核返回了一個EWOULDBLOCK錯誤.在第四個recvfrom中,數據已經就緒了,而且已經被拷貝到咱們的應用程序的緩衝區了,內核返回一個OK,而後咱們的應用程序處理這些數據.
咱們能夠看到,在這種模型中,咱們須要使用輪詢的方式來肯定數據究竟是否就緒.儘管這會浪費CPU時間,可是仍然是比較常見的模型,通常是在系統函數中用到.
I/O複用(I/O Multiplexing)
在I/O多路複用中,咱們會調用select()或者poll(),而且阻塞在這兩個系統調用上.而不是阻塞在recvfrom這個實際的IO操做的系統調用上.下面是I/O多路複用模型的過程圖:
從上圖中,咱們能夠看到,咱們會阻塞在select()這個系統調用上,並等待數據到達.當select()告訴咱們數據到達時,再經過recvfrom系統調用將數據拷貝到應用程序的緩衝區.多了一次系統調用,確實是I/O多路複用模型的缺點.可是存在即合理,它也有優勢.
它的優勢在於,select能夠同時監聽多個文件描述符,以及感興趣的事件.因此,咱們能夠在一個線程中完成以前須要好多個線程才能完成的事情.
好比,咱們想要同時從一個接受來自Socket的數據,以及從文件中讀數據.在阻塞IO模型中,咱們會這麼作:
1.建立一個線程A,在其中建立一個Socket Server,並經過它的accept()方法,等待客戶端的鏈接並處理數據 2.建立一個線程B,在其中打開文件而且讀數據.
這就須要兩個線程,對吧?
並且咱們又知道,線程之間的切換是有開銷的,也是須要涉及到用戶態到內核態的轉換.
而咱們在I/O多路複用模型中,能夠這樣作:
1.經過註冊函數告訴系統,應用程序對於Socket的讀事件以及文件的讀事件感興趣 2.經過輪詢調用select()方法,查看哪些咱們感興趣的事件已經發生了 3.在同一個線程中,依次進行對應的操做
咱們能夠看到,在這裏咱們只須要用一個線程就能夠作到在阻塞IO中咱們須要兩個線程才能作到的事情.這就是I/O複用中的複用的含義.
信號驅動IO(signal driven I/O)
信號驅動IO使用信號量機制,它告訴內核,當文件描述符準備就緒時,經過SIGIO信號通知咱們.過程以下:
咱們首先經過sigaction系統調用安裝一個事件處理器.這個操做會當即返回.因此咱們的應用程序會繼續運行,而不會阻塞.當數據準備就緒時,內核會給咱們的應用程序發出一個SIGIO信號,咱們能夠繼續進行下面的處理:在信號處理器中,經過recvfrom系統調用將數據從內核緩衝區讀取到應用程序緩衝區中,告訴應用程序從緩衝區讀取數據而且處理.這種模型的優勢是,在等待數據就緒時,應用程序並不會被阻塞.應用程序能夠繼續運行,只須要在數據就緒時,讓時間處理器通知它便可.
異步IO(Asynchronous IO)
異步IO模型跟事件驅動IO模型相似,也是告訴內核,在必定狀況下通知咱們.可是它跟事件驅動IO模型不一樣的是,在事件驅動IO模型中,內核會在數據就緒,即數據被拷貝到內核緩衝區時,通知咱們.而在異步IO中,內核會在整個操做都被完成,即數據從內核緩衝區拷貝到應用程序緩衝區時,通知咱們.以下圖所示: