C++那些問題

參考鏈接:

-在設(shè)計(jì)類的時(shí)候,一個(gè)原則就是對(duì)于不改變數(shù)據(jù)成員的成員函數(shù)都要在后面加 const,而對(duì)于改變數(shù)據(jù)成員的成員函數(shù)不能加 const。所以 const 關(guān)鍵字對(duì)成員函數(shù)的行為作了更加明確的限定:有 const 修飾的成員函數(shù)(指 const 放在函數(shù)參數(shù)表的后面,而不是在函數(shù)前面或者參數(shù)表內(nèi)),只能讀取數(shù)據(jù)成員,不能改變數(shù)據(jù)成員;沒有 const 修飾的成員函數(shù),對(duì)數(shù)據(jù)成員則是可讀可寫的。

除此之外,在類的成員函數(shù)后面加 const 還有什么好處呢?
“獲得能力:可以操作常量對(duì)象”,其實(shí)應(yīng)該是常量(即 const)對(duì)象可以調(diào)用 const 成員函數(shù),而不能調(diào)用非const修飾的函數(shù)。

對(duì)于const成員函數(shù),"不能修改類的數(shù)據(jù)成員,不能在函數(shù)中調(diào)用其他不是const的函數(shù)",這是由const的屬性決定的 。

  • static靜態(tài)成員變量不能在類的內(nèi)部初始化。在類的內(nèi)部只是聲明,定義必須在類定義體的外部,通常在類的實(shí)現(xiàn)文件中初始化。

  • 由于聲明為static的變量只被初始化一次,因?yàn)樗鼈冊(cè)趩为?dú)的靜態(tài)存儲(chǔ)中分配了空間,因此類中的靜態(tài)變量由對(duì)象共享。對(duì)于不同的對(duì)象,不能有相同靜態(tài)變量的多個(gè)副本。也是因?yàn)檫@個(gè)原因,靜態(tài)變量不能使用構(gòu)造函數(shù)初始化。

  • 類中的靜態(tài)變量應(yīng)由用戶使用類外的類名和范圍解析運(yùn)算符顯式初始化

  • 靜態(tài)對(duì)象的范圍是貫穿程序的生命周期

  • 一個(gè)對(duì)象的this指針并不是對(duì)象本身的一部分,不會(huì)影響sizeof(對(duì)象)的結(jié)果。

  • 在類的非靜態(tài)成員函數(shù)中返回類對(duì)象本身的時(shí)候,直接使用 return *this。

  • 當(dāng)參數(shù)與成員變量名相同時(shí),如this->n = n (不能寫成n = n)。

  • this在成員函數(shù)的開始執(zhí)行前構(gòu)造,在成員的執(zhí)行結(jié)束后清除。

  • this類型為const A* const。A為類。

  • 內(nèi)聯(lián)能提高函數(shù)效率,但并不是所有的函數(shù)都定義成內(nèi)聯(lián)函數(shù)!內(nèi)聯(lián)是以代碼膨脹(復(fù)制)為代價(jià),僅僅省去了函數(shù)調(diào)用的開銷,從而提高函數(shù)的執(zhí)行效率。

  • 虛函數(shù)可以是內(nèi)聯(lián)函數(shù),內(nèi)聯(lián)是可以修飾虛函數(shù)的,但是當(dāng)虛函數(shù)表現(xiàn)多態(tài)性的時(shí)候不能內(nèi)聯(lián)。

  • 內(nèi)聯(lián)是在編譯器建議編譯器內(nèi)聯(lián),而虛函數(shù)的多態(tài)性在運(yùn)行期,編譯器無法知道運(yùn)行期調(diào)用哪個(gè)代碼,因此虛函數(shù)表現(xiàn)為多態(tài)性時(shí)(運(yùn)行期)不可以內(nèi)聯(lián)。

  • inline virtual 唯一可以內(nèi)聯(lián)的時(shí)候是:編譯器知道所調(diào)用的對(duì)象是哪個(gè)類(如 Base::who()),這只有在編譯器具有實(shí)際對(duì)象而不是對(duì)象的指針或引用時(shí)才會(huì)發(fā)生。

  • sizeof: 普通繼承,派生類繼承了所有基類的函數(shù)與成員,要按照字節(jié)對(duì)齊來計(jì)算大小

  • sizeof:虛函數(shù)繼承,不管是單繼承還是多繼承,都是繼承了基類的vptr。(32位操作系統(tǒng)4字節(jié),64位操作系統(tǒng) 8字節(jié))!

  • sizeof: 靜態(tài)變量不影響類的大小

  • sizeof:對(duì)于包含虛函數(shù)的類,不管有多少個(gè)虛函數(shù),只有一個(gè)虛指針,vptr的大小。

  • sizeof: 派生類虛繼承多個(gè)虛函數(shù),會(huì)繼承所有虛函數(shù)的vptr。

  • 抽象類中:在成員函數(shù)內(nèi)可以調(diào)用純虛函數(shù),在構(gòu)造函數(shù)/析構(gòu)函數(shù)內(nèi)部不能使用純虛函數(shù)。

  • 如果一個(gè)類從抽象類派生而來,它必須實(shí)現(xiàn)了基類中的所有純虛函數(shù),才能成為非抽象類。

  • 抽象類至少包含一個(gè)純虛函數(shù)

  • 不能創(chuàng)建抽象類的對(duì)象

  • 抽象類的指針和引用 指向 由抽象類派生出來的類的對(duì)象

  • 派生類沒有實(shí)現(xiàn)純虛函數(shù),那么派生類也會(huì)變?yōu)槌橄箢悾荒軇?chuàng)建抽象類的對(duì)象

  • 虛函數(shù)的調(diào)用取決于指向或者引用的對(duì)象的類型,而不是指針或者引用自身的類型。

  • 默認(rèn)參數(shù)是靜態(tài)綁定的,虛函數(shù)是動(dòng)態(tài)綁定的。 默認(rèn)參數(shù)的使用需要看指針或者引用本身的類型,而不是對(duì)象的類型。

  • 靜態(tài)函數(shù)不可以聲明為虛函數(shù),同時(shí)也不能被const 和 volatile關(guān)鍵字修飾

  • 為什么構(gòu)造函數(shù)不可以為虛函數(shù)?
    解:盡管虛函數(shù)表vtable是在編譯階段就已經(jīng)建立的,但指向虛函數(shù)表的指針vptr是在運(yùn)行階段實(shí)例化對(duì)象時(shí)才產(chǎn)生的。 如果類含有虛函數(shù),編譯器會(huì)在構(gòu)造函數(shù)中添加代碼來創(chuàng)建vptr。 問題來了,如果構(gòu)造函數(shù)是虛的,那么它需要vptr來訪問vtable,可這個(gè)時(shí)候vptr還沒產(chǎn)生。 因此,構(gòu)造函數(shù)不可以為虛函數(shù)。

  • 虛函數(shù)可以被私有化,但有一些細(xì)節(jié)需要注意。
    1 基類指針指向繼承類對(duì)象,則調(diào)用繼承類對(duì)象的函數(shù);
    2 int main()必須聲明為Base類的友元,否則編譯失敗。 編譯器報(bào)錯(cuò): ptr無法訪問私有函數(shù)。
    3 當(dāng)然,把基類聲明為public, 繼承類為private,該問題就不存在了。

  • volatile 關(guān)鍵字聲明的變量,每次訪問時(shí)都必須從內(nèi)存中取出值(沒有被 volatile 修飾的變量,可能由于編譯器的優(yōu)化,從 CPU 寄存器中取值)

  • const 可以是 volatile (如只讀的狀態(tài)寄存器)

  • 指針可以是 volatile

  • 斷言,是宏,而非函數(shù)。assert 宏的原型定義在 (C)、(C++)中,其作用是如果它的條件返回錯(cuò)誤,則終止程序執(zhí)行??梢酝ㄟ^定義 NDEBUG 來關(guān)閉 assert,但是需要在源代碼的開頭,include 之前。

  • 斷言主要用于檢查邏輯上不可能的情況。

  • 它們可用于檢查代碼在開始運(yùn)行之前所期望的狀態(tài),或者在運(yùn)行完成后檢查狀態(tài)。與正常的錯(cuò)誤處理不同,斷言通常在運(yùn)行時(shí)被禁用。

  • 忽略斷言:在代碼開頭加上:#define NDEBUG // 加上這行,則 assert 不可用

  • extern "C"全部都放在于cpp程序相關(guān)文件或其頭文件中。

  • 在C中struct只單純的用作數(shù)據(jù)的復(fù)合類型,也就是說,在結(jié)構(gòu)體聲明中只能將數(shù)據(jù)成員放在里面,而不能將函數(shù)放在里面。

  • 在C結(jié)構(gòu)體聲明中不能使用C++訪問修飾符,如:public、protected、private 而在C++中可以使用。

  • 在C中定義結(jié)構(gòu)體變量,如果使用了下面定義必須加struct。

  • C的結(jié)構(gòu)體不能繼承(沒有這一概念)。

  • 若結(jié)構(gòu)體的名字與函數(shù)名相同,可以正常運(yùn)行且正常的調(diào)用!例如:可以定義與 struct Base 不沖突的 void Base() {}。

  • 與C對(duì)比如下:
    1.C++結(jié)構(gòu)體中不僅可以定義數(shù)據(jù),還可以定義函數(shù)。
    2.C++結(jié)構(gòu)體中可以使用訪問修飾符,如:public、protected、private 。
    3.C++結(jié)構(gòu)體使用可以直接使用不帶struct。
    4.C++繼承若結(jié)構(gòu)體的名字與函數(shù)名相同,可以正常運(yùn)行且正常的調(diào)用!但是定義結(jié)構(gòu)體變量時(shí)候只用帶struct的!

  • union
    1.默認(rèn)訪問控制符為 public
    2.可以含有構(gòu)造函數(shù)、析構(gòu)函數(shù)
    3.不能含有引用類型的成員
    4.不能繼承自其他類,不能作為基類
    5.不能含有虛函數(shù)
    6.匿名 union 在定義所在作用域可直接訪問 union 成員
    7.匿名 union 不能包含 protected 成員或 private 成員
    8.全局匿名聯(lián)合必須是靜態(tài)(static)的

  • .C實(shí)現(xiàn)多態(tài)

  • 封裝
    C語言中是沒有class類這個(gè)概念的,但是有struct結(jié)構(gòu)體,我們可以考慮使用struct來模擬;
    使用函數(shù)指針把屬性與方法封裝到結(jié)構(gòu)體中。
  • 繼承
    結(jié)構(gòu)體嵌套
  • 多態(tài)、
    類與子類方法的函數(shù)指針不同

在C語言的結(jié)構(gòu)體內(nèi)部是沒有成員函數(shù)的,如果實(shí)現(xiàn)這個(gè)父結(jié)構(gòu)體和子結(jié)構(gòu)體共有的函數(shù)呢?我們可以考慮使用函數(shù)指針來模擬。但是這樣處理存在一個(gè)缺陷就是:父子各自的函數(shù)指針之間指向的不是類似C++中維護(hù)的虛函數(shù)表而是一塊物理內(nèi)存,如果模擬的函數(shù)過多的話就會(huì)不容易維護(hù)了。

模擬多態(tài),必須保持函數(shù)指針變量對(duì)齊(在內(nèi)容上完全一致,而且變量對(duì)齊上也完全一致)。否則父類指針指向子類對(duì)象,運(yùn)行崩潰!

#include <stdio.h>

// 重定義一個(gè)函數(shù)指針類型
typedef void (*pf) ();

/**
   父類 
 */ 
typedef struct _A
{
    pf _f;
}A;


/**
   子類
 */
typedef struct _B
{ 
    A _b; // 在子類中定義一個(gè)基類的對(duì)象即可實(shí)現(xiàn)對(duì)父類的繼承。 
}B;

void FunA() 
{
    printf("%s\n","Base A::fun()");
}

void FunB() 
{
    printf("%s\n","Derived B::fun()");
}


int main() 
{
    A a;
    B b;

    a._f = FunA;
    b._b._f = FunB;

    A *pa = &a;
    pa->_f();
    pa = (A *)&b;   // 讓父類指針指向子類的對(duì)象,由于類型不匹配所以要進(jìn)行強(qiáng)轉(zhuǎn) 
    pa->_f();
    return 0;
}
  • explicit 修飾構(gòu)造函數(shù)時(shí),可以防止隱式轉(zhuǎn)換和復(fù)制初始化
  • explicit 修飾轉(zhuǎn)換函數(shù)時(shí),可以防止隱式轉(zhuǎn)換,但按語境轉(zhuǎn)換除外
#include <iostream>

using namespace std;

struct A
{
    A(int) { }
    operator bool() const { return true; }
};

struct B
{
    explicit B(int) {}
    explicit operator bool() const { return true; }
};

void doA(A a) {}

void doB(B b) {}

int main()
{
    A a1(1);        // OK:直接初始化
    A a2 = 1;        // OK:復(fù)制初始化
    A a3{ 1 };        // OK:直接列表初始化
    A a4 = { 1 };        // OK:復(fù)制列表初始化
    A a5 = (A)1;        // OK:允許 static_cast 的顯式轉(zhuǎn)換 
    doA(1);            // OK:允許從 int 到 A 的隱式轉(zhuǎn)換
    if (a1);        // OK:使用轉(zhuǎn)換函數(shù) A::operator bool() 的從 A 到 bool 的隱式轉(zhuǎn)換
    bool a6(a1);        // OK:使用轉(zhuǎn)換函數(shù) A::operator bool() 的從 A 到 bool 的隱式轉(zhuǎn)換
    bool a7 = a1;        // OK:使用轉(zhuǎn)換函數(shù) A::operator bool() 的從 A 到 bool 的隱式轉(zhuǎn)換
    bool a8 = static_cast<bool>(a1);  // OK :static_cast 進(jìn)行直接初始化

    B b1(1);        // OK:直接初始化
//    B b2 = 1;        // 錯(cuò)誤:被 explicit 修飾構(gòu)造函數(shù)的對(duì)象不可以復(fù)制初始化
    B b3{ 1 };        // OK:直接列表初始化
//    B b4 = { 1 };        // 錯(cuò)誤:被 explicit 修飾構(gòu)造函數(shù)的對(duì)象不可以復(fù)制列表初始化
    B b5 = (B)1;        // OK:允許 static_cast 的顯式轉(zhuǎn)換
//    doB(1);            // 錯(cuò)誤:被 explicit 修飾構(gòu)造函數(shù)的對(duì)象不可以從 int 到 B 的隱式轉(zhuǎn)換
    if (b1);        // OK:被 explicit 修飾轉(zhuǎn)換函數(shù) B::operator bool() 的對(duì)象可以從 B 到 bool 的按語境轉(zhuǎn)換
    bool b6(b1);        // OK:被 explicit 修飾轉(zhuǎn)換函數(shù) B::operator bool() 的對(duì)象可以從 B 到 bool 的按語境轉(zhuǎn)換
//    bool b7 = b1;        // 錯(cuò)誤:被 explicit 修飾轉(zhuǎn)換函數(shù) B::operator bool() 的對(duì)象不可以隱式轉(zhuǎn)換
    bool b8 = static_cast<bool>(b1);  // OK:static_cast 進(jìn)行直接初始化

    return 0;
}
  • 友元提供了一種 普通函數(shù)或者類成員函數(shù) 訪問另一個(gè)類中的私有或保護(hù)成員 的機(jī)制。也就是說有兩種形式的友元:

(1)友元函數(shù):普通函數(shù)對(duì)一個(gè)訪問某個(gè)類中的私有或保護(hù)成員。

(2)友元類:類A中的成員函數(shù)訪問類B中的私有或保護(hù)成員

優(yōu)點(diǎn):提高了程序的運(yùn)行效率。

缺點(diǎn):破壞了類的封裝性和數(shù)據(jù)的透明性。

總結(jié): - 能訪問私有成員 - 破壞封裝性 - 友元關(guān)系不可傳遞 - 友元關(guān)系的單向性 - 友元聲明的形式及數(shù)量不受限制

在類聲明的任何區(qū)域中聲明,而定義則在類的外部。
friend <類型><友元函數(shù)名>(<參數(shù)表>);
注意,友元函數(shù)只是一個(gè)普通函數(shù),并不是該類的類成員函數(shù),它可以在任何地方調(diào)用,友元函數(shù)中通過對(duì)象名來訪問該類的私有或保護(hù)成員。

#include <iostream>

using namespace std;

class A
{
public:
    A(int _a):a(_a){};
    friend int geta(A &ca);  ///< 友元函數(shù)
private:
    int a;
};

int geta(A &ca) 
{
    return ca.a;
}

int main()
{
    A a(3);    
    cout<<geta(a)<<endl;

    return 0;
}

友元類的聲明在該類的聲明中,而實(shí)現(xiàn)在該類外。
friend class <友元類名>;
類B是類A的友元,那么類B可以直接訪問A的私有成員。

#include <iostream>

using namespace std;

class A
{
public:
    A(int _a):a(_a){};
    friend class B;
private:
    int a;
};

class B
{
public:
    int getb(A ca) {
        return  ca.a; 
    };
};

int main() 
{
    A a(3);
    B b;
    cout<<b.getb(a)<<endl;
    return 0;
}

友元關(guān)系沒有繼承性 假如類B是類A的友元,類C繼承于類A,那么友元類B是沒辦法直接訪問類C的私有或保護(hù)成員。

友元關(guān)系沒有傳遞性 假如類B是類A的友元,類C是類B的友元,那么友元類C是沒辦法直接訪問類A的私有或保護(hù)成員,也就是不存在“友元的友元”這種關(guān)系。

  • using
#include <iostream>
#define isNs1 1
//#define isGlobal 2
using namespace std;
void func() 
{
    cout<<"::func"<<endl;
}

namespace ns1 {
    void func()
    {
        cout<<"ns1::func"<<endl; 
    }
}

namespace ns2 {
#ifdef isNs1 
    using ns1::func;    /// ns1中的函數(shù)
#elif isGlobal
    using ::func; /// 全局中的函數(shù)
#else
    void func() 
    {
        cout<<"other::func"<<endl; 
    }
#endif
}

int main() 
{
    /**
     * 這就是為什么在c++中使用了cmath而不是math.h頭文件
     */
    ns2::func(); // 會(huì)根據(jù)當(dāng)前環(huán)境定義宏的不同來調(diào)用不同命名空間下的func()函數(shù)
    return 0;
}
class Base{
public:
 std::size_t size() const { return n;  }
protected:
 std::size_t n;
};
class Derived : private Base {
public:
 using Base::size;
protected:
 using Base::n;
};

在繼承過程中,派生類可以覆蓋重載函數(shù)的0個(gè)或多個(gè)實(shí)例,一旦定義了一個(gè)重載版本,那么其他的重載版本都會(huì)變?yōu)椴豢梢姟?/p>

如果對(duì)于基類的重載函數(shù),我們需要在派生類中修改一個(gè),又要讓其他的保持可見,必須要重載所有版本,這樣十分的繁瑣。

#include <iostream>
using namespace std;

class Base{
    public:
        void f(){ cout<<"f()"<<endl;
        }
        void f(int n){
            cout<<"Base::f(int)"<<endl;
        }
};

class Derived : private Base {
    public:
        using Base::f;
        void f(int n){
            cout<<"Derived::f(int)"<<endl;
        }
};

int main()
{
    Base b;
    Derived d;
    d.f();
    d.f(1);
    return 0;
}

如上代碼中,在派生類中使用using聲明語句指定一個(gè)名字而不指定形參列表,所以一條基類成員函數(shù)的using聲明語句就可以把該函數(shù)的所有重載實(shí)例添加到派生類的作用域中。此時(shí),派生類只需要定義其特有的函數(shù)就行了,而無需為繼承而來的其他函數(shù)重新定義。

C中常用typedef A B這樣的語法,將B定義為A類型,也就是給A類型一個(gè)別名B

對(duì)應(yīng)typedef A B,使用using B=A可以進(jìn)行同樣的操作。

typedef vector<int> V1; 
using V2 = vector<int>;
  • 全局作用域符(::name):用于類型名稱(類、類成員、成員函數(shù)、變量等)前,表示作用域?yàn)槿置臻g

  • 類作用域符(class::name):用于表示指定類型的作用域范圍是具體某個(gè)類的

  • 命名空間作用域符(namespace::name):用于表示指定類型的作用域范圍是具體某個(gè)命名空間的

  • enum:
    傳統(tǒng)問題:

    • 作用域不受限,會(huì)容易引起命名沖突。例如下面無法編譯通過的:
#include <iostream>
using namespace std;

enum Color {RED,BLUE};
enum Feeling {EXCITED,BLUE};

int main() 
{
    return 0;
}
  • 會(huì)隱式轉(zhuǎn)換為int
  • 用來表征枚舉變量的實(shí)際類型不能明確指定,從而無法支持枚舉類型的前向聲明。

解決作用域不受限帶來的命名沖突問題的一個(gè)簡單方法是,給枚舉變量命名時(shí)加前綴,如上面例子改成 COLOR_BLUE 以及 FEELING_BLUE。

namespace Color 
{
    enum Type
    {
        RED=15,
        YELLOW,
        BLUE
    };
};

這樣之后就可以用 Color::Type c = Color::RED; 來定義新的枚舉變量了。如果 using namespace Color 后,前綴還可以省去,使得代碼簡化。不過,因?yàn)槊臻g是可以隨后被擴(kuò)充內(nèi)容的,所以它提供的作用域封閉性不高。在大項(xiàng)目中,還是有可能不同人給不同的東西起同樣的枚舉類型名。

更“有效”的辦法是用一個(gè)類或結(jié)構(gòu)體來限定其作用域,例如:定義新變量的方法和上面命名空間的相同。不過這樣就不用擔(dān)心類在別處被修改內(nèi)容。這里用結(jié)構(gòu)體而非類,一是因?yàn)楸旧硐M@些常量可以公開訪問,二是因?yàn)樗话瑪?shù)據(jù)沒有成員函數(shù)。

struct Color1
{
    enum Type
    {
        RED=102,
        YELLOW,
        BLUE
    };
};

C++11 標(biāo)準(zhǔn)中引入了“枚舉類”(enum class),可以較好地解決上述問題。

  • 新的enum的作用域不在是全局的
  • 不能隱式轉(zhuǎn)換成其他類型
/**
 * @brief C++11的枚舉類
 * 下面等價(jià)于enum class Color2:int
 */
enum class Color2
{
    RED=2,
    YELLOW,
    BLUE
};
r2 c2 = Color2::RED;
cout << static_cast<int>(c2) << endl; //必須轉(zhuǎn)!
  • 可以指定用特定的類型來存儲(chǔ)enum
enum class Color3:char;  // 前向聲明

// 定義
enum class Color3:char 
{
    RED='r',
    BLUE
};
char c3 = static_cast<char>(Color3::RED);
class Person{
public:
    typedef enum {
        BOY = 0,
        GIRL
    }SexType;
};
//訪問的時(shí)候通過,Person::BOY或者Person::GIRL來進(jìn)行訪問。
  • 枚舉常量不會(huì)占用對(duì)象的存儲(chǔ)空間,它們?cè)诰幾g時(shí)被全部求值。

  • 枚舉常量的缺點(diǎn)是:它的隱含數(shù)據(jù)類型是整數(shù),其最大值有限,且不能表示浮點(diǎn)。

  • 這里的括號(hào)是必不可少的,decltype的作用是“查詢表達(dá)式的類型”,因此,上面語句的效果是,返回 expression 表達(dá)式的類型。注意,decltype 僅僅“查詢”表達(dá)式的類型,并不會(huì)對(duì)表達(dá)式進(jìn)行“求值”。
decltype (expression)
int i = 4;
decltype(i) a; //推導(dǎo)結(jié)果為int。a的類型為int。
using size_t = decltype(sizeof(0));//sizeof(a)的返回值為size_t類型
using ptrdiff_t = decltype((int*)0 - (int*)0);
using nullptr_t = decltype(nullptr);
vector<int >vec;
typedef decltype(vec.begin()) vectype;
for (vectype i = vec.begin; i != vec.end(); i++)
{
//...
}
//在C++中,我們有時(shí)候會(huì)遇上一些匿名類型,如:
struct 
{
    int d ;
    doubel b;
}anon_s;
//而借助decltype,我們可以重新使用這個(gè)匿名的結(jié)構(gòu)體:

decltype(anon_s) as ;//定義了一個(gè)上面匿名的結(jié)構(gòu)體
泛型編程中結(jié)合auto,用于追蹤函數(shù)的返回值類型

這也是decltype最大的用途了。
template <typename T>
auto multiply(T x, T y)->decltype(x*y)
{
    return x*y;
}

對(duì)于decltype(e)而言,其判別結(jié)果受以下條件的影響:

如果e是一個(gè)沒有帶括號(hào)的標(biāo)記符表達(dá)式或者類成員訪問表達(dá)式,那么的decltype(e)就是e所命名的實(shí)體的類型。此外,如果e是一個(gè)被重載的函數(shù),則會(huì)導(dǎo)致編譯錯(cuò)誤。 否則 ,假設(shè)e的類型是T,如果e是一個(gè)將亡值,那么decltype(e)為T&& 否則,假設(shè)e的類型是T,如果e是一個(gè)左值,那么decltype(e)為T&。 否則,假設(shè)e的類型是T,則decltype(e)為T。

標(biāo)記符指的是除去關(guān)鍵字、字面量等編譯器需要使用的標(biāo)記之外的程序員自己定義的標(biāo)記,而單個(gè)標(biāo)記符對(duì)應(yīng)的表達(dá)式即為標(biāo)記符表達(dá)式。例如:
int arr[4]
則arr為一個(gè)標(biāo)記符表達(dá)式,而arr[3]+0不是。

int i = 4;
int arr[5] = { 0 };
int *ptr = arr;
struct S{ double d; }s ;
void Overloaded(int);
void Overloaded(char);//重載的函數(shù)
int && RvalRef();
const bool Func(int);

//規(guī)則一:推導(dǎo)為其類型
decltype (arr) var1; //int 標(biāo)記符表達(dá)式

decltype (ptr) var2;//int *  標(biāo)記符表達(dá)式

decltype(s.d) var3;//doubel 成員訪問表達(dá)式

//decltype(Overloaded) var4;//重載函數(shù)。編譯錯(cuò)誤。

//規(guī)則二:將亡值。推導(dǎo)為類型的右值引用。

decltype (RvalRef()) var5 = 1;

//規(guī)則三:左值,推導(dǎo)為類型的引用。

decltype ((i))var6 = i;     //int&

decltype (true ? i : i) var7 = i; //int&  條件表達(dá)式返回左值。

decltype (++i) var8 = i; //int&  ++i返回i的左值。

decltype(arr[5]) var9 = i;//int&. []操作返回左值

decltype(*ptr)var10 = i;//int& *操作返回左值

decltype("hello")var11 = "hello"; //const char(&)[9]  字符串字面常量為左值,且為const左值。


//規(guī)則四:以上都不是,則推導(dǎo)為本類型

decltype(1) var12;//const int

decltype(Func(1)) var13=true;//const bool

decltype(i++) var14 = i;//int i++返回右值
  • 右值引用可實(shí)現(xiàn)轉(zhuǎn)移語義(Move Sementics)和精確傳遞(Perfect Forwarding),它的主要目的有兩個(gè)方面:

    1.消除兩個(gè)對(duì)象交互時(shí)不必要的對(duì)象拷貝,節(jié)省運(yùn)算存儲(chǔ)資源,提高效率。
    2.能夠更簡潔明確地定義泛型函數(shù)。

引用折疊
  • X& &、X& &&、X&& & 可折疊成 X&
  • X&& && 可折疊成 X&&
  • C++的引用在減少了程序員自由度的同時(shí)提升了內(nèi)存操作的安全性和語義的優(yōu)美性。

  • private 是完全私有的,只有當(dāng)前類中的成員能訪問到.

  • protected 是受保護(hù)的,只有當(dāng)前類的成員與繼承該類的類才能訪問.

  • 在使用new的時(shí)候做了兩件事:

1、調(diào)用operator new分配空間

2、調(diào)用構(gòu)造函數(shù)初始化對(duì)象

  • 在使用delete的時(shí)候也做了兩件事:

1、調(diào)用析構(gòu)函數(shù)清理對(duì)象

2、調(diào)用operator delete函數(shù)釋放空間

  • 在使用new[N]的時(shí)候也做了兩件事:

1、調(diào)用operator new分配空間

2、調(diào)用N次構(gòu)造函數(shù)初始化N個(gè)對(duì)象

  • 在使用delete[]的時(shí)候也做了兩件事:

1、調(diào)用N次析構(gòu)函數(shù)清理N個(gè)對(duì)象

2、調(diào)用operator delete函數(shù)釋放空間

  • 1.1 字符串化操作符(#)

在一個(gè)宏中的參數(shù)前面使用一個(gè)#,預(yù)處理器會(huì)把這個(gè)參數(shù)轉(zhuǎn)換為一個(gè)字符數(shù)組,換言之就是:#是“字符串化”的意思,出現(xiàn)在宏定義中的#是把跟在后面的參數(shù)轉(zhuǎn)換成一個(gè)字符串。

  • 1.2 符號(hào)連接操作符(##)

“##”是一種分隔連接方式,它的作用是先分隔,然后進(jìn)行強(qiáng)制連接。將宏定義的多個(gè)形參轉(zhuǎn)換成一個(gè)實(shí)際參數(shù)名。

注意事項(xiàng):

(1)當(dāng)用##連接形參時(shí),##前后的空格可有可無。
(2)連接后的實(shí)際參數(shù)名,必須為實(shí)際存在的參數(shù)名或是編譯器已知的宏定義。
(3)如果##后的參數(shù)本身也是一個(gè)宏的話,##會(huì)阻止這個(gè)宏的展開。

  • 1.3 續(xù)行操作符(\

當(dāng)定義的宏不能用一行表達(dá)完整時(shí),可以用”\”表示下一行繼續(xù)此宏的定義。
注意 \ 前留空格。

template<typename ...Args>
class A {
private:
    int size = 0;    // c++11 支持類內(nèi)初始化
public:
    A() {
        size = sizeof...(Args);
        cout << size << endl;
    }
};

 A<int, string, vector<int>> a;    // 類型任意

 // Tuple就是利用這個(gè)特性(變長參數(shù)模板)
tuple<int, string> t = make_tuple(1, "hha");

單例模式

//簡單實(shí)現(xiàn),只適用于單線程下。
//懶漢 線程不安全
class singleton {
private:
    singleton() {}
    static singleton *p;
public:
    static singleton *instance();
};

singleton *singleton::p = nullptr;

singleton* singleton::instance() {
    if (p == nullptr)
        p = new singleton();
    return p;
}
//餓漢 這個(gè)是線程安全的
class singleton {
private:
    singleton() {}
    static singleton *p;
public:
    static singleton *instance();
};

singleton *singleton::p = new singleton();
singleton* singleton::instance() {
    return p;
}

//多線程下單例模式
class singleton {
private:
    singleton() {}
    static singleton *p;
    static mutex lock_;
public:
    static singleton *instance();
};

singleton *singleton::p = nullptr;

singleton* singleton::instance() {
    lock_guard<mutex> guard(lock_);
    if (p == nullptr)
        p = new singleton();
    return p;
}
//雙重檢查鎖+自動(dòng)回收
class singleton {
private:
    singleton() {}

    static singleton *p;
    static mutex lock_;
public:
    singleton *instance();

    // 實(shí)現(xiàn)一個(gè)內(nèi)嵌垃圾回收類
    class CGarbo
    {
    public:
        ~CGarbo()
        {
            if(singleton::p)
                delete singleton::p;
        }
    };
    static CGarbo Garbo; // 定義一個(gè)靜態(tài)成員變量,程序結(jié)束時(shí),系統(tǒng)會(huì)自動(dòng)調(diào)用它的析構(gòu)函數(shù)從而釋放單例對(duì)象
};

singleton *singleton::p = nullptr;
singleton::CGarbo Garbo;

singleton* singleton::instance() {
    if (p == nullptr) {
        lock_guard<mutex> guard(lock_);
        if (p == nullptr)
            p = new singleton();
    }
    return p;
}

5.memory barrier指令

DCLP問題在C++11中,這個(gè)問題得到了解決。

因?yàn)樾碌腃++11規(guī)定了新的內(nèi)存模型,保證了執(zhí)行上述3個(gè)步驟的時(shí)候不會(huì)發(fā)生線程切換,相當(dāng)這個(gè)初始化過程是“原子性”的的操作,DCL又可以正確使用了,不過在C++11下卻有更簡潔的多線程singleton寫法了,這個(gè)留在后面再介紹。

C++11之前解決方法是barrier指令。要使其正確執(zhí)行的話,就得在步驟2、3直接加上一道m(xù)emory barrier。強(qiáng)迫CPU執(zhí)行的時(shí)候按照1、2、3的步驟來運(yùn)行。

第一種實(shí)現(xiàn):

基于operator new+placement new,遵循1,2,3執(zhí)行順序依次編寫代碼。

// method 1 operator new + placement new
singleton *instance() {
    if (p == nullptr) {
        lock_guard<mutex> guard(lock_);
        if (p == nullptr) {
            singleton *tmp = static_cast<singleton *>(operator new(sizeof(singleton)));
            new(p)singleton();
            p = tmp;
        }
    }
    return p;
}

**第二種實(shí)現(xiàn):**

基于直接嵌入ASM匯編指令mfence,uninx的barrier宏也是通過該指令實(shí)現(xiàn)的。

#define barrier() __asm__ volatile ("lwsync")
singleton *singleton::instance() {
    if (p == nullptr) {
        lock_guard<mutex> guard(lock_);
        barrier();
        if (p == nullptr) {
            p = new singleton();
        }
    }
    return p;
}

通常情況下是調(diào)用cpu提供的一條指令,這條指令的作用是會(huì)阻止cpu將該指令之前的指令交換到該指令之后,這條指令也通常被叫做barrier。 上面代碼中的asm表示這個(gè)是一條匯編指令,volatile是可選的,如果用了它,則表示向編譯器聲明不允許對(duì)該匯編指令進(jìn)行優(yōu)化。lwsync是POWERPC提供的barrier指令。

  • Scott Meyer在《Effective C++》中提出了一種簡潔的singleton寫法
singleton *singleton::instance() {
    static singleton p;
    return &p;
}
mutex singleton::lock_;
atomic<singleton *> singleton::p;

/*
* std::atomic_thread_fence(std::memory_order_acquire); 
* std::atomic_thread_fence(std::memory_order_release);
* 這兩句話可以保證他們之間的語句不會(huì)發(fā)生亂序執(zhí)行。
*/
singleton *singleton::instance() {
    singleton *tmp = p.load(memory_order_relaxed);
    atomic_thread_fence(memory_order_acquire);
    if (tmp == nullptr) {
        lock_guard<mutex> guard(lock_);
        tmp = p.load(memory_order_relaxed);
        if (tmp == nullptr) {
            tmp = new singleton();
            atomic_thread_fence(memory_order_release);
            p.store(tmp, memory_order_relaxed);
        }
    }
    return p;
}

值得注意的是,上述代碼使用兩個(gè)比較關(guān)鍵的術(shù)語,獲得與釋放:

  • 獲得是一個(gè)對(duì)內(nèi)存的讀操作,當(dāng)前線程的任何后面的讀寫操作都不允許重排到這個(gè)操作的前面去。
  • 釋放是一個(gè)對(duì)內(nèi)存的寫操作,當(dāng)前線程的任何前面的讀寫操作都不允許重排到這個(gè)操作的后面去。

如果是在unix平臺(tái)的話,除了使用atomic operation外,在不適用C++11的情況下,還可以通過pthread_once來實(shí)現(xiàn)Singleton。

原型如下:
int pthread_once(pthread_once_t once_control, void (init_routine) (void));

class singleton {
private:
    singleton(); //私有構(gòu)造函數(shù),不允許使用者自己生成對(duì)象
    singleton(const singleton &other);

    //要寫成靜態(tài)方法的原因:類成員函數(shù)隱含傳遞this指針(第一個(gè)參數(shù))
    static void init() {
        p = new singleton();
    }

    static pthread_once_t ponce_;
    static singleton *p; //靜態(tài)成員變量 
public:
    singleton *instance() {
        // init函數(shù)只會(huì)執(zhí)行一次
        pthread_once(&ponce_, &singleton::init);
        return p;
    }
};
image
  • C++ tr1全稱Technical Report 1,是針對(duì)C++標(biāo)準(zhǔn)庫的第一次擴(kuò)展。即將到來的下一個(gè)版本的C++標(biāo)準(zhǔn)c++0x會(huì)包括它,以及一些語言本身的擴(kuò)充。tr1包括大家期待已久的smart pointer,正則表達(dá)式以及其他一些支持范型編程的內(nèi)容。草案階段,新增的類和模板的名字空間是std::tr1。
#include <tr1/array>
std::tr1::array<int ,10> a;//第一個(gè)類型,第二個(gè)大小
  • deque與vector最大的差異就是:

    1.deque允許于常數(shù)時(shí)間內(nèi)對(duì)頭端進(jìn)行插入或刪除元素;

    2.deque是分段連續(xù)線性空間,隨時(shí)可以增加一段新的空間;

  • 用戶看起來deque使用的是連續(xù)空間,實(shí)際上是分段連續(xù)線性空間。為了管理分段空間deque容器引入了map,稱之為中控器,map是一塊連續(xù)的空間,其中每個(gè)元素是指向緩沖區(qū)的指針,緩沖區(qū)才是deque存儲(chǔ)數(shù)據(jù)的主體。


    image

    在上圖中,buffer稱為緩沖區(qū),顯示map size的一段連續(xù)空間就是中控器。
    中控器包含了map size,指向buffer的指針,deque的開始迭代器與結(jié)尾迭代器。

_Tp     **_M_map;
size_t      _M_map_size;
iterator    _M_start;
iterator    _M_finish;

deque是使用基類_Deque_base來完成內(nèi)存管理與中控器管理

  • 在stack的源碼中我們關(guān)注兩點(diǎn): - 默認(rèn)_Sequence為deque - 內(nèi)部函數(shù)實(shí)現(xiàn)是調(diào)用_Sequence對(duì)應(yīng)容器的函數(shù)。

  • 對(duì)于stack來說,底層容器可以是vector、deque、list,但不可以是map、set。 由于編譯器不會(huì)做全面性檢查,當(dāng)調(diào)用函數(shù)不存在的時(shí)候,就編譯不通過,所以對(duì)于像set雖然不能作為底層容器,但如果具有某些函數(shù),調(diào)用仍然是成功的,直到調(diào)用的函數(shù)不存在。

  • 在queue的源碼中我們關(guān)注兩點(diǎn): - 默認(rèn)_Sequence為deque - 內(nèi)部函數(shù)實(shí)現(xiàn)是調(diào)用_Sequence對(duì)應(yīng)容器的函數(shù)。

  • 優(yōu)先隊(duì)列則是使用vector作為默認(rèn)容器。

  • 對(duì)于queue底層容器可以是deque,也可以是list,但不能是vector,map,set,使用默認(rèn)的deque效率在插入方面比其他容器作為底層要快!

  • 對(duì)于優(yōu)先隊(duì)列來說,測(cè)試結(jié)果發(fā)現(xiàn),采用deque要比默認(rèn)的vector插入速度快! 底層支持vector、deque容器,但不支持list、map、set。

  • stack、queue、priority_queue不被稱為容器, 把它稱為容器配接器。

  • vector的數(shù)據(jù)安排以及操作方式,與array非常相似。兩者的唯一差別在于空間的運(yùn)用的靈活性,array是靜態(tài)的,一旦配置了就不能改變,而 vector是動(dòng)態(tài)空間,隨著元素的加入,它的內(nèi)部機(jī)制會(huì)自行擴(kuò)充空間以容納新元素。

  • 類作用域

在類外部訪問類中的名稱時(shí),可以使用類作用域操作符,形如MyClass::name的調(diào)用通常存在三種:靜態(tài)數(shù)據(jù)成員、靜態(tài)成員函數(shù)和嵌套類型

struct MyClass {
    static int A; //靜態(tài)成員
    static int B(){cout<<"B()"<<endl; return 100;} //靜態(tài)函數(shù)
    typedef int C;  //嵌套類型
    struct A1 { //嵌套類型
        static int s;
    };
};
image
  • 一種能夠順序訪問容器中每個(gè)元素的方法,使用該方法不能暴露容器內(nèi)部的表達(dá)方式。而類型萃取技術(shù)就是為了要解決和 iterator 有關(guān)的問題的。

  • 總結(jié):通過定義內(nèi)嵌類型,我們獲得了知曉 iterator 所指元素類型的方法,通過 traits 技法,我們將函數(shù)模板對(duì)于原生指針和自定義 iterator 的定義都統(tǒng)一起來,我們使用 traits 技法主要是為了解決原生指針和自定義 iterator 之間的不同所造成的代碼冗余,這就是 traits 技法的妙處所在。

  • 因?yàn)榭疹愅瑯涌梢员粚?shí)例化,每個(gè)實(shí)例在內(nèi)存中都有一個(gè)獨(dú)一無二的地址,為了達(dá)到這個(gè)目的,編譯器往往會(huì)給一個(gè)空類隱含的加一個(gè)字節(jié),這樣空類在實(shí)例化后在內(nèi)存得到了獨(dú)一無二的地址.所以上述大小為1.

  • 兩個(gè)不同對(duì)象的地址不同。

  • 基類為空,通過繼承方式來獲得基類的功能,并沒有產(chǎn)生額外大小的優(yōu)化稱之為EBO(空基類優(yōu)化)。

  • 第一種方式的內(nèi)存管理:嵌入一個(gè)內(nèi)存管理類

template<class T, class Allocator>
class MyContainerNotEBO {
    T *data_ = nullptr;
    std::size_t capacity_;
    Allocator allocator_;   // 嵌入一個(gè)MyAllocator
public:
    MyContainerNotEBO(std::size_t capacity)
            : capacity_(capacity), allocator_(), data_(nullptr) {
        std::cout << "alloc malloc" << std::endl;
        data_ = reinterpret_cast<T *>(allocator_.allocate(capacity * sizeof(T))); // 分配內(nèi)存
    }

    ~MyContainerNotEBO() {
        std::cout << "MyContainerNotEBO free malloc" << std::endl;
        allocator_.deallocate(data_);
    }
};
  • 第二種方式:采用空基類優(yōu)化,繼承來獲得內(nèi)存管理功能
template<class T, class Allocator>
class MyContainerEBO
        : public Allocator {    // 繼承一個(gè)EBO
    T *data_ = nullptr;
    std::size_t capacity_;
public:
    MyContainerEBO(std::size_t capacity)
            : capacity_(capacity), data_(nullptr) {
        std::cout << "alloc malloc" << std::endl;
        data_ = reinterpret_cast<T *>(this->allocate(capacity * sizeof(T)));
    }

    ~MyContainerEBO() {
        std::cout << "MyContainerEBO free malloc" << std::endl;
        this->deallocate(data_);
    }
};

int main() {
    MyContainerNotEBO<int, MyAllocator> notEbo = MyContainerNotEBO<int, MyAllocator>(0);
    std::cout << "Using Not EBO Test sizeof is " << sizeof(notEbo) << std::endl;
    MyContainerEBO<int, MyAllocator> ebo = MyContainerEBO<int, MyAllocator>(0);
    std::cout << "Using EBO Test sizeof is " << sizeof(ebo) << std::endl;

    return 0;
}

//結(jié)果
alloc malloc
Using Not EBO Test sizeof is 24
alloc malloc
Using EBO Test sizeof is 16
MyContainerEBO free malloc
MyContainerNotEBO free malloc
  • 采用EBO的設(shè)計(jì)確實(shí)比嵌入設(shè)計(jì)好很多。

? 二分查找的速度比簡單查找快得多。
? O(log n)比O(n)快。需要搜索的元素越多,前者比后者就快得越多。
? 算法運(yùn)行時(shí)間并不以秒為單位。
? 算法運(yùn)行時(shí)間是從其增速的角度度量的。
? 算法運(yùn)行時(shí)間用大O表示法表示。

  • set/multiset以rb_tree為底層結(jié)構(gòu),因此有元素自動(dòng)排序特性。排序的依據(jù)是key,而set/multiset元素的value和key合二為一:value就是key。

  • 第一個(gè)問題:key是value,value也是key。

  • 第二個(gè)問題:無法使用迭代器改變?cè)刂怠?/p>

  • 第三個(gè)問題:插入是唯一的key。

cout<<"flag: "<<itree._M_insert_unique(5).second<<endl;  // 學(xué)習(xí)返回值
typedef pair<int ,bool> _Res;    // 也來用一下typedef后的pair
cout<<_Res(1,true).first<<endl;  // 直接包裹
_Res r=make_pair(2,false);    // 定義新對(duì)象
cout<<r.first<<endl;   // 輸出結(jié)果
  • map的key為key,value為key+data,與set是不同的,set是key就是value,value就是key。
  • map的key不可修改,map與multimap的插入調(diào)用函數(shù)不同,影響了其key是否對(duì)應(yīng)value。
  • initializer_list使用
  • map有[]操作符,而multimap沒有[]操作符。
insert的幾種方法:

(1) 插入 pair


std::pair<iterator, bool> insert(const value_type& __x)
{ return _M_t._M_insert_unique(__x); }
map里面

(2) 在指定位置,插入pair


iterator insert(iterator __position, const value_type& __x)
{ return _M_t._M_insert_equal_(__position, __x); }
(3) 從一個(gè)范圍進(jìn)行插入


template<typename _InputIterator>
void
insert(_InputIterator __first, _InputIterator __last)
{ _M_t._M_insert_equal(__first, __last); }
(4)從list中插入


void
insert(initializer_list<value_type> __l)
{ this->insert(__l.begin(), __l.end()); }
針對(duì)最后一個(gè)insert,里面有個(gè)initializer_list,舉個(gè)例子大家就知道了。
  • 結(jié)論1:undered_map與undered_set不允許key重復(fù),而帶multi的則允許key重復(fù);
  • 結(jié)論2:undered_map與undered_multimap采用的迭代器是iterator,而undered_set與undered_multiset采用的迭代器是const_iterator。
  • 結(jié)論3:undered_map與undered_multimap的key是key,value是key+value;而undered_set與undered_multiset的key是Value,Value也是Key。

五種創(chuàng)建線程的方式

  • 函數(shù)指針
  • Lambda函數(shù)吧
  • Functor(仿函數(shù))
  • 非靜態(tài)成員函數(shù)
  • 靜態(tài)成員函數(shù)

2.1 函數(shù)指針

// 1.函數(shù)指針
void fun(int x) {
    while (x-- > 0) {
        cout << x << endl;
    }
}
// 調(diào)用
std::thread t1(fun, 10);
t1.join();

2.2 Lambda函數(shù)

// 注意:如果我們創(chuàng)建多線程 并不會(huì)保證哪一個(gè)先開始
int main() {
    // 2.Lambda函數(shù)
    auto fun = [](int x) {
        while (x-- > 0) {
            cout << x << endl;
        }
    };
//    std::1.thread t1(fun, 10);
    // 也可以寫成下面:
    std::thread t1_1([](int x) {
        while (x-- > 0) {
            cout << x << endl;
        }
    }, 11);
//    std::1.thread t2(fun, 10);
//    t1.join();
    t1_1.join();
//    t2.join();
    return 0;
}

2.3 仿函數(shù)

// 3.functor (Funciton Object)
class Base {
public:
    void operator()(int x) {
        while (x-- > 0) {
            cout << x << endl;
        }
    }
};
// 調(diào)用
thread t(Base(), 10);
t.join();

2.4 非靜態(tài)成員函數(shù)

// 4.Non-static member function
class Base {
public:
    void fun(int x) {
        while (x-- > 0) {
            cout << x << endl;
        }
    }
};
// 調(diào)用
thread t(&Base::fun,&b, 10);
t.join();

2.5 靜態(tài)成員函數(shù)

// 4.Non-static member function
class Base {
public:
    static void fun(int x) {
        while (x-- > 0) {
            cout << x << endl;
        }
    }
};
// 調(diào)用
thread t(&Base::fun, 10);
t.join();

join

  • 一旦線程開始,我們要想等待線程完成,需要在該對(duì)象上調(diào)用join()
  • 雙重join將導(dǎo)致程序終止
  • 在join之前我們應(yīng)該檢查顯示是否可以被join,通過使用joinable()
void run(int count) {
    while (count-- > 0) {
        cout << count << endl;
    }
    std::this_thread::sleep_for(chrono::seconds(3));
}

int main() {
    thread t1(run, 10);
    cout << "main()" << endl;
    t1.join();
    if (t1.joinable()) {
        t1.join();
    }
    cout << "main() after" << endl;
    return 0;
}

detach

  • 這用于從父線程分離新創(chuàng)建的線程
  • 在分離線程之前,請(qǐng)務(wù)必檢查它是否可以joinable,否則可能會(huì)導(dǎo)致兩次分離,并且雙重detach()將導(dǎo)致程序終止
  • 如果我們有分離的線程并且main函數(shù)正在返回,那么分離的線程執(zhí)行將被掛起
void run(int count) {
    while (count-- > 0) {
        cout << count << endl;
    }
    std::this_thread::sleep_for(chrono::seconds(3));
}

int main() {
    thread t1(run, 10);
    cout << "main()" << endl;
    t1.detach();
    if(t1.joinable())
        t1.detach();
    cout << "main() after" << endl;
    return 0;
  • 增加變量(i ++)的過程分三個(gè)步驟:

      1.將內(nèi)存內(nèi)容復(fù)制到CPU寄存器。 load
      2.在CPU中增加該值。 increment
      3.將新值存儲(chǔ)在內(nèi)存中。 store
    
  • 如果只能通過一個(gè)線程訪問該內(nèi)存位置(例如下面的變量i),則不會(huì)出現(xiàn)爭用情況,也沒有與i關(guān)聯(lián)的臨界區(qū)。 但是sum變量是一個(gè)全局變量,可以通過兩個(gè)線程進(jìn)行訪問。 兩個(gè)線程可能會(huì)嘗試同時(shí)增加變量。
#include <iostream>
#include <mutex>
#include <thread>

using namespace std;

int sum = 0; //shared

mutex m;

void *countgold() {
    int i; //local to each thread
    for (i = 0; i < 10000000; i++) {
        sum += 1;
    }
    return NULL;
}

int main() {
    thread t1(countgold);
    thread t2(countgold);

    //Wait for both threads to finish
    t1.join();
    t2.join();

    cout << "sum = " << sum << endl;
    return 0;
}

初始化列表與賦值

  • const成員的初始化只能在構(gòu)造函數(shù)初始化列表中進(jìn)行
  • 引用成員的初始化也只能在構(gòu)造函數(shù)初始化列表中進(jìn)行
  • 對(duì)象成員(對(duì)象成員所對(duì)應(yīng)的類沒有默認(rèn)構(gòu)造函數(shù))的初始化,也只能在構(gòu)造函數(shù)初始化列表中進(jìn)行

類之間嵌套

第一種: 使用初始化列表。

class Animal {
public:
    Animal() {
        std::cout << "Animal() is called" << std::endl;
    }

    Animal(const Animal &) {
        std::cout << "Animal (const Animal &) is called" << std::endl;
    }

    Animal &operator=(const Animal &) {
        std::cout << "Animal & operator=(const Animal &) is called" << std::endl;
        return *this;
    }

    ~Animal() {
        std::cout << "~Animal() is called" << std::endl;
    }
};

class Dog {
public:
    Dog(const Animal &animal) : __animal(animal) {
        std::cout << "Dog(const Animal &animal) is called" << std::endl;
    }

    ~Dog() {
        std::cout << "~Dog() is called" << std::endl;
    }

private:
    Animal __animal;
};

int main() {
    Animal animal;
    std::cout << std::endl;
    Dog d(animal);
    std::cout << std::endl;
    return 0;
}

第二種:構(gòu)造函數(shù)賦值來初始化對(duì)象。

Dog(const Animal &animal) {
    __animal = animal;
    std::cout << "Dog(const Animal &animal) is called" << std::endl;
}
  • 類中包含其他自定義的class或者struct,采用初始化列表,實(shí)際上就是創(chuàng)建對(duì)象同時(shí)并初始化
  • 而采用類中賦值方式,等價(jià)于先定義對(duì)象,再進(jìn)行賦值,一般會(huì)先調(diào)用默認(rèn)構(gòu)造,在調(diào)用=操作符重載函數(shù)。
無默認(rèn)構(gòu)造函數(shù)的繼承關(guān)系中
class Animal {
public:
    Animal(int age) {
        std::cout << "Animal(int age) is called" << std::endl;
    }

    Animal(const Animal & animal) {
        std::cout << "Animal (const Animal &) is called" << std::endl;
    }

    Animal &operator=(const Animal & amimal) {
        std::cout << "Animal & operator=(const Animal &) is called" << std::endl;
        return *this;
    }

    ~Animal() {
        std::cout << "~Animal() is called" << std::endl;
    }
};

class Dog : Animal {
public:
    Dog(int age) : Animal(age) {
        std::cout << "Dog(int age) is called" << std::endl;
    }

    ~Dog() {
        std::cout << "~Dog() is called" << std::endl;
    }

};
  • 由于在Animal中沒有默認(rèn)構(gòu)造函數(shù),所以報(bào)錯(cuò),遇到這種問題屬于災(zāi)難性的,我們應(yīng)該盡量避免,可以通過初始化列表給基類的構(gòu)造初始化。

類中const數(shù)據(jù)成員、引用數(shù)據(jù)成員

  • 特別是引用數(shù)據(jù)成員,必須用初始化列表初始化,而不能通過賦值初始化!
class Animal {
public:
    Animal(int age, std::string name) : age_(age), name_(name) {
    std::cout << "Animal(int age) is called" << std::endl;
}
private:
    int &age_;
    const std::string name_;
};
// enum class
enum class EntityType {
    Ground = 0,
    Human,
    Aerial,
    Total
};

void foo(EntityType entityType)
{
    if (entityType == EntityType::Ground) {
        /*code*/
    }
}
  • 可以指定對(duì)象具有構(gòu)造函數(shù)和析構(gòu)函數(shù),這些構(gòu)造函數(shù)和析構(gòu)函數(shù)在適當(dāng)?shù)臅r(shí)候由
  • 編譯器自動(dòng)調(diào)用,這為管理給定對(duì)象的內(nèi)存提供了更為方便的方法。
    • 資源在析構(gòu)函數(shù)中被釋放
    • 該類的實(shí)例是堆棧分配的
    • 資源是在構(gòu)造函數(shù)中獲取的。

RAII代表“資源獲取是初始化”。常見的例子有:

  • 文件操作

  • 智能指針

  • 互斥量

  • 為了使用copy-swap,我們需要三件事:
    1.一個(gè)有效的拷貝構(gòu)造函數(shù)
    2.一個(gè)有效的析構(gòu)函數(shù)(兩者都是任何包裝程序的基礎(chǔ),因此無論如何都應(yīng)完整)以及交換功能。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容