c++

static的做用html

​ static修飾變量只能在本範圍內可見(由external變爲internal,做用域和連接屬性並無改變):修飾全局變量只能在本cpp文件中可見,修飾局部變量只能在該代碼塊內可見。修飾類的靜態成員在類的對象中共享這一份數據。ios

c++中的智能指針

​ 其實就是一個類,當銷燬指向的內存時,能夠不用手動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。多線程

智能指針主要用於管理在堆上分配的內存,它將普通的指針封裝爲一個棧對象。當棧對象的生存週期結束後,會在析構函數中釋放掉申請的內存,從而防止內存泄漏。閉包

在main()函數以前執行

​ C++ 的全局對象的構造函數會在 main 函數以前先運行,其實在 c 語言裏面很早就有啦,在 gcc 中可使用 attribute 關鍵字指定以下(在編譯器編譯的時候就絕決定了)

map和set的實現,有什麼區別

​ 它們的底層都是用紅黑樹實現的,關於紅黑樹和avl樹的區別(首先紅黑樹是不符合AVL樹的平衡條件的,即每一個節點的左子樹和右子樹的高度最多差1的二叉查找樹。可是提出了爲節點增長顏色,紅黑是用非嚴格的平衡來換取增刪節點時候旋轉次數的下降,任何不平衡都會在三次旋轉以內解決,而AVL是嚴格平衡樹,所以在增長或者刪除節點的時候,根據不一樣狀況,旋轉的次數比紅黑樹要多。因此紅黑樹的插入效率更高!!!

  • 區別
    1. map是key-value鍵值對的形式存儲,set是關鍵字的集合。
    2. set的迭代器是const的,不容許修改元素的值;map不是,容許修改value。
    3. map支持下標操做,set不支持

memset()函數

​ extern void *memset(void *buffer, int c, int count) ;

+ buffer:指針或者數組
	+ c:賦給buffer的值
	+ count:buffer的長度

​ 通常用來給一段內存空間所有設置爲某個字符。

c++通信

​ 管道、系統IPC(信號、信號量、共享內存、消息隊列)、套接字socket
進程和線程共享地址空間,進程的堆共享給線程,可是每一個線程有本身獨立的棧

參考進程和線程之間的通信差異

內核線程和用戶線程區別

多線程之間的通信方式

互斥量、信號量、臨界區

多線程之間的鎖

每一個進程的地址空間是獨立的,位於一個進程的普通內存區域中的對象是沒法被其它進程所訪問的,能知足這一要求的內存區域是共享內存,於是同步對象要在進程的共享內存區域內建立。同步對象還能夠放在文件中。同步對象能夠比建立它的進程具備更長的生命週期。

1. std::lock_guard

 		2. std::unique_lock
             		3. std::condition_variable
  • 互斥鎖(mutex)
#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;
  }
}
  • 條件鎖(cond):容許線程以一種無競爭的方式等待某個條件的發生。當該條件沒有發生時,線程會一直處於休眠狀態。當被其它線程通知條件已經發生時,線程纔會被喚醒從而繼續向下執行。條件變量是比較底層的同步原語,直接使用的狀況很少,每每用於實現高層之間的線程同步。使用條件變量的一個經典的例子就是線程池(Thread Pool)了。
#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;
}
  1. 首先,這在一個局部做用域內, std::lock_guard 在構造時,會調用 g_mutex->lock() 方法;

  2. 局部做用域代碼結束後, std:;lock_guard 的析構函數會被調用,函數中會調用 g_mutex->unlock() 方法。

  3. 當線程調用 g_cond.wait(lock) 前要先手動調用 lock->lock() ,這裏是經過 std::unique_lock 的構造方法實現的;

  4. 當線程調用 g_cond.wait(lock) 進入等待後,會調用 lock->unlock() 方法,因此這也是前面構造lock時使用了 std::unique_lock ;

  5. 通知使用的 g_cond.notify_one() ,這個能夠通知一個線程,另外還有 g_cond.notify_all() 用於通知全部線程;

  6. 線程收到通知的代碼放在一個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;
}
  • 讀寫鎖(rdlock)
  • 信號量(semophore):經過精心設計信號量的PV操做,能夠實現很複雜的進程同步狀況(例如經典的哲學家就餐問題和理髮店問題)。而現實的程序設計中,卻極少有人使用信號量。能用信號量解決的問題彷佛總能用其它更清晰更簡潔的設計手段去代替信號量。

windows系統中臨界區(Critical Section)、事件對象(Event)

c++11新特性

  • 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; };
    • caputrue是捕獲列表;
    • params是參數表;
    • opt是函數選項;mutable,exception,attribute。mutable說明lambda表達式體內的代碼能夠修改被捕獲的變量,而且能夠訪問被捕獲的對象的non-const方法。
      exception說明lambda表達式是否拋出異常以及何種異常。
      attribute用來聲明屬性。
    • ret是返回值類型(拖尾返回類型)
    • body是函數體

    捕獲列表:lambda表達式的捕獲列表精細控制了lambda表達式可以訪問的外部變量,以及如何訪問這些變量。

    1. []不捕獲任何變量
    2. [&]捕獲外部做用域中全部變量,並做爲引用在函數體中使用(按引用捕獲)。
    3. [=]捕獲外部做用域中的全部變量,並做爲副本在函數體中使用(按值捕獲)。注意值捕獲的前提是變量能夠拷貝,且被捕獲的變量在 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
    1. [=,&foo]按值捕獲外部做用域中全部變量,並按引用捕獲foo變量。
    2. [bar]按值捕獲bar變量,同時不捕獲其餘變量。
    3. [this]捕獲當前類中的this指針,讓lambda表達式擁有和當前類成員函數一樣的訪問權限。若是已經使用了&或者=,就默認添加此選項。捕獲this的目的是能夠在lamda中使用當前類的成員函數和成員變量
    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)
}

C++ STL

  • 容器

    • 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;
    }

    img

    針對 capacity 這個屬性,STL 中的其餘容器,如 list map set deque,因爲這些容器的內存是散列分佈的,所以不會發生相似 realloc() 的調用狀況,所以咱們能夠認爲 capacity 屬性針對這些容器是沒有意義的,所以設計時這些容器沒有該屬性。

    在 STL 中,擁有 capacity 屬性的容器只有 vector 和 string。

系統IO模型

+ 阻塞IO(Blocking IO)

img

​ 在這個例子中,咱們會經過UDP而不是TCP來舉例,由於對於UDP來講,等待數據就緒這一步更加直觀:要不就是收到了一個數據報,要不就是沒收到一個數據報.可是對於TCP來講,還有不少額外的變量.

上圖中的recvfrom是一個系統調用.當咱們執行一次系統調用的時候,有一次從用戶態到內核態的切換.

從上圖中咱們能夠看到,進程調用recvfrom以後,這個系統調用並不會當即返回,它會等到數據報到達而且被拷貝到應用程序的緩衝區中,或者出現了一個錯誤,纔會返回.咱們稱這個過程是阻塞的,應用程序只有在數據報被放入緩衝區以後,才能繼續進行.

  • 非阻塞IO(Nonblocking IO)

    非阻塞IO和阻塞IO相對,它會告訴內核,"當我要你完成的IO操做不能完成時,不要讓進程阻塞,你給我返回一個錯誤就好了".過程以下圖所示:

    img

    在上面的三個recvfrom操做中,因爲數據並無就緒,因此內核返回了一個EWOULDBLOCK錯誤.在第四個recvfrom中,數據已經就緒了,而且已經被拷貝到咱們的應用程序的緩衝區了,內核返回一個OK,而後咱們的應用程序處理這些數據.

    咱們能夠看到,在這種模型中,咱們須要使用輪詢的方式來肯定數據究竟是否就緒.儘管這會浪費CPU時間,可是仍然是比較常見的模型,通常是在系統函數中用到.

  • I/O複用(I/O Multiplexing)

    在I/O多路複用中,咱們會調用select()或者poll(),而且阻塞在這兩個系統調用上.而不是阻塞在recvfrom這個實際的IO操做的系統調用上.下面是I/O多路複用模型的過程圖:

    img

    從上圖中,咱們能夠看到,咱們會阻塞在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信號通知咱們.過程以下:

    img

    咱們首先經過sigaction系統調用安裝一個事件處理器.這個操做會當即返回.因此咱們的應用程序會繼續運行,而不會阻塞.當數據準備就緒時,內核會給咱們的應用程序發出一個SIGIO信號,咱們能夠繼續進行下面的處理:在信號處理器中,經過recvfrom系統調用將數據從內核緩衝區讀取到應用程序緩衝區中,告訴應用程序從緩衝區讀取數據而且處理.這種模型的優勢是,在等待數據就緒時,應用程序並不會被阻塞.應用程序能夠繼續運行,只須要在數據就緒時,讓時間處理器通知它便可.

  • 異步IO(Asynchronous IO)

    異步IO模型跟事件驅動IO模型相似,也是告訴內核,在必定狀況下通知咱們.可是它跟事件驅動IO模型不一樣的是,在事件驅動IO模型中,內核會在數據就緒,即數據被拷貝到內核緩衝區時,通知咱們.而在異步IO中,內核會在整個操做都被完成,即數據從內核緩衝區拷貝到應用程序緩衝區時,通知咱們.以下圖所示:

    img

    img

美團面試題

  1. 若是線上某臺虛機CPU Load太高,該如何快速排查緣由?只介紹思路和涉及的Linux命令便可 。
  2. 請簡要描述MySQL數據庫聯合索引的命中規則,可舉例說明。
  3. 什麼是分佈式事務,分佈式事務產生的緣由是什麼?分佈式事務的解決方案有哪些?分別有哪些優缺點?
    答案
  4. 請描述https的請求過程。
  5. 什麼是事務傳播行爲?你知道Spring事務中都有哪些傳播類型嗎?如何使用/指定傳播類型?
  6. IO設計中Reactor 和 Proactor 區別。

相關文章
相關標籤/搜索