C++ 入門指南

1.內置數據類型

1.1 字面量

整形字面量

12爲10進制數、012爲8進制數、0x12爲16進制數、10L爲長整型、10LL爲longlong類型、10uL爲無符號長整形、10u爲無符號數、

浮點型字面量

3.14爲小數、3.14e3爲科學計數法小數、3.14f爲單精度小數。

2.變量

2.1 定義和聲明

變量只能定義一次,但可以聲明多次。

int a; 變量定義。如果在函數外也就是全局變量會有默認的初始值;在函數內部也就是局部變量沒有初始值(未定義值)。

int a=1; 變量定義並初始化。

extern int i; 變量聲明。可以在其他文件找到這個變量。

class Hello; 類聲明。

void hello(int); 函數聲明。

2.2 初始化

string str; 默認初始化。調用默認初始化函數。

string str = "hello"; 賦值初始化。調用拷貝構造函數

string str("hello"); 構造函數初始化。調用對應的構造函數。

vector<int> arr{1,2,3}; 列表初始化。用於容器和數組。

2.3 引用和指針

int &b = a; b爲引用。

int *b = &a; b爲指針。 

引用必須初始化而且不能被修改;初始化不能是字面量,因爲引用是變量的別名。

指針不用立即初始化而且可以被修改;也就是可以指向其他的對象。

int *p[3]; 爲int *的數組。

int (*p)[3]; 爲指向int [3]的指針。

初始化空指針

int *b=0; int *b=NULL; int *b=nullptr;

空類型指針

void *pv; 可以指向任何類型的對象。

2.4 const限定符

const int p=1; 爲int常量。不能修改p的值而且p必須初始化。

const int *p;爲指向常量int的指針。p不必初始化,但p指向的對象不能被修改。

int *const p; 爲指向int的常量指針。p必須初始化而且p的值不能被修改。但p指向的對象可以被修改。

const int *constp; 爲指向常量int的常量指針。p必須初始化而且p的值和p指向的值不能被修改。

常量指針不能賦值給非常量指針。非常量指針可以賦值給常量指針。

常量可以賦值給非常量(值傳遞)。非常量可以賦值給常量。

2.5 constexpr 變量

const變量的初始值並不一定是常量表達式。也就是說用這個表達式聲明其他常量會與上一次聲明的常量的值不一樣。聲明constexpr類型可以幫助編譯器來驗證變量的值是否爲常量表達式。

constexpr int a = 20; // 20是常量表達式

constexpr int a= a + 2; // a + 2是常量表達式。

函數體內的變量不能聲明爲constexpr。因爲是局部變量,地址不固定。

constexpr int *p; 爲指向int的常量指針並不是指向常量int。

2.6 類型別名

typedef int a; a爲int的別名。

using a = int; a爲int的別名。

2.7 auto和decltype

auto類型會根據初始化值來判斷變量類型。

decltype 會根據值來得到值的類型。

auto a = 10;  a的類型爲int。

decltype(1+1) a; a的類型爲int。

auto會忽略const修飾符;只獲取初始化值的類型。

const int i= 1;

auto int a = i; a的類型爲int而不是const int。

decltype不會忽略const和&修飾符。

const int i=1, *p=&i;

decltype(i) a= 2; a的類型是const int

decltype(*p) a=i; a的類型是int &引用。

2.8 自定義數據結構

struct Hello {

 int a; // public

} aa, *pp;

class Hello {

 int a; // private

}aa, *pp;

聲明struct和class後面必須有分號;

struct和class唯一的區別是struct默認是public成員,class默認是private成員。

3. 字符串、向量和數組

3.1 字符串基本操作

3.1.1 字符串基本方法

str.empty() str爲空返回true,非空返回false

str.size() 返回字符個數

str[n] 返回第n個字符的引用.

str1+str2 拼接字符串

str1=str2 拷貝字符串

==、!=、>、>=、<、<= 比較字符串

3.1.2 字符處理

cctype頭文件的定義了處理字符的函數。

isalnum(ch) 判斷字符ch是否爲字母或者數字

isalpha(ch)  判斷字符ch是否爲字母

iscntrl(ch) 判斷字符ch是否爲控制字母

isdigit(ch) 判斷字符ch是否爲數字

isgraph(ch) 判斷字符ch是否可以打印且不是空格

islower(ch) 判斷字符ch是否是小寫字母。

isprint(ch) 判斷字符ch是否可以打印

isspace(ch) 判斷字符ch是否爲空白(空格、回車、換行等)

isupper(ch) 判斷字符ch是否爲大寫字母

isxdigit(ch) 判斷字符ch是否爲十六進制字符

tolower(ch) 轉換爲小寫字母

toupper(ch) 轉換爲大寫字母

3.1.3 處理字符串的字符

for(auto &ch : str) {}

ch爲char &類型,可以直接修改str的字符。

3.1.4 字符串構造

string s1;                // 空字符串
string s1 = "123";        // 拷貝構造
string s1("123");         // 拷貝構造
string s1(chs, n);        // 拷貝字符數組前n個字符
string s1(s2, pos);       // 拷貝pos位置之後的字符串
string s1(s2, pos, len);  // 拷貝pos位置之後len長度的字符串。  

3.1.5 字符串操作

s.substr(pos)                 // 獲取子字符串
s.substr(pos,len)
s.insert(pos,args)            // 插入字符串
s.assign(args);               // 字符串賦值
s.append(args);               // 追加字符串
s.replace(pos,len,args);      // 替換range範圍內字符串
s.erase(pos,len);             // 刪除字符串
s.find(args);                 // 從頭部查找   
s.rfind(args);                // 從尾部查找
s.find_frist_of(args);        // 從頭部查找args的任意一個字符
s.find_last_of(args);         // 從尾部查找args的任意一個字符
s.find_frist_not_of(args);
s.find_last_not_of(args);
s1.compare(s2)                // 比較字符串
to_string(i)                  // 數字轉字符串
stod(s)                       // 字符串轉浮點型
stoi(s)
stoul(s)
stoll(s)
stof(s)

pos爲下標或者迭代器
args一下形式
str            // 字符串
str,pos,len    // 子字符串
cp,len         // 子字符數組
cp             // 字符數組
n,c            // 那n個元素的字符,find不支持
b,e            // 另一個字符串的迭代器範圍,find不支持
初始化列表

3.2 向量vector的基本操作

3.2.1 定義和初始化vector

vector<int > v1; 初始化空vector

vector<int> v1 = v2; 拷貝vector

vector<int> v1(v2); 拷貝構造vector

vector<int> v1(n, val); n個重複的val值

vector<int> v1(n); n個默認值

vector<int> v1{1,2,3}; 列表初始化

vector<int> v1 = {1,2,3}; 列表初始化賦值

vertor<int> v1(biter,eiter); 使用另一個數組或者容器的迭代器初始化(類型int必須一致)

3.2.2 vector基本方法

v.empty(); 是否包含元素

v.size(); 返回元素個數

v.push_back(val); 添加元素

v[n]; 返回位置爲n的引用。

==、!=、>、>=、<、<= 向量比較

3.2.3 迭代器

auto a = v.begin();  指向第一個元素的迭代器

auto a = begin(v); 指向第一個元素的迭代器

auth b = v.end(); 指向最後一個元素的下一個元素(尾後)的迭代器

auth b = end(v);

*iter; 迭代器指向元素的引用。

iter->men; 相當於(*iter).men;

++iter; 指向下一個元素

--iter; 指向上一個元素

iter + n; 向前移動n個元素

iter - n; 向後移動n個元素

iter += n;

iter -= n;

iter1 - iter2; iter2向前移動到iter1的元素個數

iter1 == iter2 迭代器是否相等。

iter1 != iter2 

>、>=、<、<=

3.3 數組

3.3.1 數組定義和初始化

int a[] = {1,2,3}

int a[2];

int a[5] = {1,2}

int a[3][4][5] = {1,2,3};

3.2.2 數組和指針

數組名爲指向第一個元素的指針。數組不能被拷貝

int *a = arr; 正確。

int a[] = arr; 錯誤。

int *a[5] 爲指向int的指針數組

int (*a)[5] 爲指向int[5]的指針

4. 類型轉換

4.1 static_cast

只能進行類型兼容的轉換。

算術的類型轉換。

double val = static_cast<double>(j) / i;

空類型指針轉換。

void *p;

double *pd = static_cast<double*>(p);

類型不兼容轉換

int a=1;

string b = static_cast<string>(a); 錯誤

4.2 const_cast

常量轉換爲非常量。不能轉換類型。

const char *pc;

char *p = const_cast<char *>(pc);

4.3 reinterpret_cast

可以轉換類型不同的指針

int *i;

int string *p = reinterpret_cast<string *>(i)

5. 函數

5.1 函數聲明

int hello(int,int);

5.2 形參

值傳遞。不影響實參。

hello(int a);

引用傳遞。影響實參。

void hello(int &a);

指針傳遞。影響實參。

void hello(int *a);

如果不改變實參,儘量使用常量引用。

void hello(const string &str)

5.3 靜態局部變量

靜態局部變量定義後就不會重複初始化了。直到程序結束才釋放。

5.3 返回值

函數內部的局部變量的引用和指針和引用不能被返回。但是可以返回值(值傳遞)。

可以使用尾置返回類型。需要用auto放到原來返回值的位置。

auto bb(int a) -> int
{
  return 1;
}

5.4 函數重載

函數名相同,形參列表不同的函數。成爲函數重載。

void hello();
void hello(int i);
void hello(string a, int i);

注意:函數重載不區分const

void hello(int a);
void hello(const int a); // 重複定義

5.5 默認參數

void hello(int a=1, int b=2, string c ="hello");
hello();
hello(1);
hello(1,2);
hello(1,2,"world");

5.6 內聯函數

內聯函數在調用點可以直接展開。

inline void hello() {
    cout << "hello world" << endl;
}

調用hello();最終會被cout<< "hello world" << endl替換。

5.7 函數指針

int (*pf)(int a, int b); // pf爲函數指針
int *pf(int a, int b); // pf爲返回值是int指針的函數
void fun(int a, int b, void pf(int a, int b)); // 函數指針作爲形參

5.8 initializer_list

void hello(initializer_list<string> il)
{
  for (auto &value : il)
  {
    cout << value << endl;
  }
}
hello({"hello", "wolrd"});

6 類

類有數據成員和成員函數。

6.1 構造函數

6.1.1 默認構造函數

編譯器會自動生成默認的構造函數。可以使用=default表示使用默認生成的構造函數。

class Hello
{
   public: 
     Hello() = default;
};

6.1.2 構造函數初始化列表

初始化列表的初始化順序按照定義的順序。

class Hello
{
  public:
    int a;
    int b;
    Hello(int aVal, int bVal): a(aVal), b(bVal) {} 
};

6.1.3 類外部定義構造函數

class Hello
{
   public:
     int a;
     Hello(int aVal);
};

Hello::Hello(int aVal): a(aVal) {}

6.1.4 類內初始值以及構造函數使用默認值

class Hello
{
  public:
    int a=1;
    int b=1;
    Hello(int aVal, int bVal=10): a(aVal),b(bVal) {}
};

6.1.5 委託構造函數

class Hello
{
    public:
      int a;
      int b;
      Hello(int aVal, int bVal): a(aVal), b(bVal) {}
      Hello(): Hello(10, 20) {}
};

6.1.6 拷貝構造函數

系統默認會合成拷貝構造函數,拷貝每一個非static成員。拷貝賦值,非引用類型的函數入參和函數返回值都會進行拷貝初始化。

class Hello
{
  public:
    Hello(const Hello &he);  // 拷貝構造函數
};

6.1.7 賦值運算符

class Hello
{
  public:
    Hello(int aVal): a(aVal) {};
    Hello& operator=(const Hello &he)
    {
      a = he.a;
      return *this;
    };
  private:
    int a;
};

6.1.8 析構函數

在實例銷燬前執行清除操作,也會銷燬實例所用的資源。如果定義了析構函數,就必須定義拷貝構造函數和賦值運算符。

class Hello
{
  public:
    ~Hello();
};

6.1.9 阻止拷貝行爲

使用=delete告訴編譯器刪除這個函數。析構函數不能被delete

class Hello
{
  public:
    Hello(Hello &he) = delete;
};

6.1.10 定義行爲像值的類

賦值操作符必須先拷貝新值再刪除舊值。否則,當新值和舊值相同,刪除舊值,新值也被刪除。新值先拷貝不會影響到舊值的刪除。

class Hello
{
  public:
    Hello(const string &s = string()): ps(new string(s)), a(0) {};
    Hello(const Hello &he): a(he.a), ps(new string(*he.ps)){};
    Hello& operator=(Hello &he)
    {
      a = he.a;
      string *newPs = new string(*he.ps);
      delete ps;
      ps = newPs;
      return *this;
    };
    ~Hello()
    {
      delete ps;
    }
  private:
    int a;
    string *ps;
};

6.1.11 定義行爲像指針的類

副本和原實例將共享動態內存。使用引用計數類管理共享的動態內存。

class Hello
{
  public:
    Hello(const string &s = string()): ps(new string(s)), a(0), use(new int(1)) {};
    Hello(const Hello &he): a(he.a), ps(he.ps), use(he.use) {
      ++*use; // 引用計數加1
    };
    Hello& operator=(Hello &he)
    {
      a = he.a;
      ++*he.use;
      if (--*use == 0)
      {
        delete ps;
        delete use; 
      }
      ps = he.ps;
      use = he.use;
      return *this;
    };
    ~Hello()
    {
     if (--*use == 0) // 引用計數爲0
     {
       delete ps;
       delete use;
     }
    }
  private:
    int a;
    string *ps;
    int *use;
};

6.1.12 交換操作

class HasPtr
{
  friend void swap(HasPtr&, HasPtr&);
  public:
    HasPtr(int numVal): num(numVal) {}
    inline int getNum() {return num;}
  private:
    int num = 0;
};

void swap(HasPtr &lhs, HasPtr &rhs)
{
  using std::swap;
  swap(lhs.num, rhs.num);
}

6.1.13 對象移動

在某些情況下,對象拷貝後會立即銷燬,使用移動而非拷貝對象會大幅度提升性能。對象移動後一般會立即銷燬,所以移動後的對象必須能正常析構。

右值引用

使用&&來獲取右值引用。右值只綁定一個將要銷燬的對象,因此可以將右值引用的資源移動到另一個對象。右值引用通常是要求轉換的表達式、字面常量或者返回右值的表達式。左值爲對象,右值爲對象的值。

6.1.14 移動構造函數

移動構造函數不應拋出異常。因爲移動構造函數不分配內存直接修改值,拋出異常後不能被還原。需要使用noexcept告訴編譯器不會拋出異常,否則編譯器認爲會和標準庫不兼容。例如標準庫的vector需要確保push_back發送異常後,自身不會發生變化。如果沒有noexcept的保證,vector也不能確保push_back功能正常。爲了讓標準庫正常工作,移動構造函數需要使用noexcept向編譯器保證不會拋出異常。

class Hello
{
  public:
    Hello(int aVal): a(aVal) {}
    Hello(Hello &&he) noexcept
    {
      a = he.a;
    }
    inline int getA() {return a;}
  private:
    int a;
};

6.1.15 移動賦值運算符

移動賦值運算符也需要標記noexcept。

class Hello
{
  public:
    Hello(int aVal): a(aVal) {}
    Hello& operator=(Hello &&he) noexcept
    {
      a = he.a;
      return *this;
    }
    inline int getA() {return a;}
  private:
    int a;
};

6.1.16 移動迭代器

allocator<int> my_alloc;
auto p = my_alloc.allocate(4);
vector<int> numbers = {1,2,3,4};
uninitialized_copy_n(make_move_iterator(numbers.begin()), 4, p);
my_print(p, 4);

6.1.17 引用限定符

class Hello
{
  public:
    Hello(initializer_list<int> il): numbers(il) {}
    Hello& sorted() &&
    {
      cout << "&&" << endl;
      return *this;
    }
    Hello& sorted() &
    {
      cout << "&" << endl;
      return *this;
    }
  private:
    vector<int> numbers;
};

Hello h1{1,7,2,9};
auto h2 = h1.sorted(); // 使用&
auto h3 = Hello{1,8,15,9}.sorted(); // 使用&&

6.2 友元

6.2.1 友元函數

友元函數可以訪問類的私有成員和保護成員

class Hello
{
  friend void my_friend(const Hello &hello);
  private:
     int a=100;
};

void my_friend(const Hello &hello)
{
    cout<<hello.a<<end;
}

6.2.2 友元類

友元類可以訪問類的私有成員和保護成員。

class Hello_Friend;
class Hello
{
    friend Hello_Friend;  
    private:
        int a=100;
};
class Hello_Friend
{
    public:
        void print_hello(const Hello &hello)
        {
            cout<<hello.a<<endl;
        }
};

6.3 成員函數

const成員函數不能修改數據成員的值。常量實例只能訪問數據成員和調用const成員函數。

class Hello
{
  public:
    int a=100;
    void print_a() const
    {
        // a=1; 錯誤
        cout<<a<<endl;
    }
};

可變數據成員。可以被const成員函數修改

class Hello
{
  public:
    mutable int a=10;
    void set_a(int aVal) const
    {
        a = aVal;
    }
};

this指針是指向實例的指針。

class Hello
{
    public:
      Hello &run()
      {
        cout<<"run"<<endl;
        return *this;
      }
      Hello &sleep()
      {
        cout<<"sleep"<<endl;
        return *this;
      }
};

Hello hello;
hello.run().sleep();

內聯成員函數

class Hello
{
    public:
        inline void print_hello();
};

重載成員函數

class Hello
{
    public:
        void print_hello();
        void print_hello(int a);
}

6.4 類的靜態成員

靜態數據成員除了常量外只能在類外部初始化。靜態成員被所有實例共享。

class Hello
{
    public:
      static const int a = 10;
      static int b;
      static void print_hello();
};

int Hello::b=100;

靜態成員可以使用作用域運算符訪問,也可以被實例使用

Hello::a;
Hello::print_hello();

6.5 操作重載和類型轉換

6.5.1 重載運算符

一般算術運算符使用非成員函數,一元運算符使用成員函數。因爲算術運算通常會類型轉換,使用非成員函數會更靈活。

class Hello
{
  friend Hello operator+(const Hello &h1, const Hello &h2);
  public:
    Hello(int numVal): num(numVal) {}
    inline int getNum() {return num;}
    Hello& operator++()
    {
      ++num;
      return *this;
    }
    Hello operator++(int)
    {
      auto tmp = *this;
      ++*this;
      return tmp;
    }
  private:
    int num = 0;
};
Hello operator+(const Hello &h1, const Hello &h2)
{
  return h1.num + h2.num; // int會使用構造函數轉換
}

6.5.2 函數調用運算符

class Hello
{
  public:
    Hello& operator() ()
    {
      cout << "hello world" << endl;
      return *this;
    }
};

lambda是函數對象。函數、函數指針、lambdab表達式、bind創建的對象以及重載了函數運算符的類都是可調用對象。可調用對象可以使用function類型表示

void my_func(){}
Hello f1;
auto f2 = [&]() {};
auto f3 = my_func;
auto *f4 = my_func;
function<void()> f(f1);
f = f2;
f = f3;
f = f4;

6.5.3 類型轉換運算符

通常轉換bool類型。因爲存在隱式的類型轉換,可能會出現意想不到的結果。在構造函數使用explicit可以避免隱式轉換。

class Hello
{
  public:
    int num;
    // explicit Hello(int numVal = 0): num(numVal) {}
    Hello(int numVal = 0): num(numVal) {}
    operator int () const
    {
      return num;
    }
};

Hello h1 = 9;  // 使用Hello(int) 構造函數。如果使用explicit,必須使用Hello(9)。
int b = h1 + 8; // 轉換成int類型

6.6 面向對象

6.6.1 繼承

類的成員分公有成員、保護成員、私有成員。公有成員可見,保護成員派生類可見,私有成員自己可見。繼承分爲public、protected、private繼承。public繼承;基類成員訪問權限不變。protected繼承;基類的共有公員變保存成員。private繼承;基類的所有成員變私有成員。

使用final禁止繼承。final類不能作爲基類也不能作爲派生類。

class Hello final
{};

派生類對象賦值給基類對象

class Hello
{};
class Hello2: public Hello
{};
Hello h1 = Hello2();
Hello &&h1 = Hello2();
Hello2 h2;
Hello &h1 = h2;

友元不能被繼承

class Hello
{
  friend class HelloFriend;
  private:
    int num = 100;
};

class HelloFriend
{
  public:
    virtual void hello(Hello &h) const
    {
      cout << h.num << endl;
    }
};

class HelloFriend2: public HelloFriend
{
  public:
    void hello(Hello &h) const override
    {
      cout << h.num << endl; // 錯誤
    }
};

使用using改變個別成員的可訪問性

class Hello
{
  friend class HelloFriend;
  public:
    int num = 100;
};

class Hello2: private Hello
{
  public:
    using Hello::num;
};

struct默認使用public繼承;class默認使用private繼承

struct Hello: Base // public繼承
class Hello: Base // private繼承

6.6.2 動態綁定

動態綁定的函數會在運行時才選擇函數版本。通常是基類定義虛函數,子類重寫虛函數,基類引用子類對象或者基類指針指向子類對象。基類的析構函數必須定義爲虛函數。

class Hello
{
  public:
    virtual void hello() const;
    virtual ~Hello() = default;
};
class Hello2: public Hello
{
  public:
    void hello() const override
    {
      cout << "hello2" << endl;
    }
};
Hello2 h2;
Hello &h1 = h2;
h1.hello();
Hello *h3 = &h2;
h3->hello();

final和override

final函數不允許派生類覆蓋,override會標記函數重寫基類函數,編輯器會檢查基類是否有對應的函數被重寫,減少程序出錯。

class Hello2: public Hello
{
  public:
    void hello() const override final // 派生類不能再重寫
    {
      cout << "hello2" << endl;
    }
};

使用作用域運算符迴避虛函數

h1.Hello::hello();

派生類的作用域嵌套在基類的作用域內。如果不是引用類型,會從聲明類型作用域查找名字,如果不能找到會從基類的作用域查找,直到找到名字。找到名字後會檢查類型是否匹配。如果是引用類型會判斷是否是重載的函數並從對象找到重載函數。派生類的成員只要名字和基類相同,不管類型是否一樣,基類的成員都會被派生類隱藏。但是重載函數必須和基類的虛函數入參一樣。

class Hello
{
  public:
    int num = 100;
    virtual void hello() 
    {
      cout << "hello" << endl;
    }
};
class Hello2: public Hello
{
  public:
    double num = 3.14; // 覆蓋基類的num,即使類型不一樣
    void hello(int a) // 不能覆蓋基類的hello
    {
      cout << a << endl;
    }
};
Hello2 h2;
Hello &h = h2;
h.hello();

6.6.3 抽象基類

含有純虛函數的類是抽象基類。抽象基類不能被實例化,只能被派生類繼承並重寫純虛函數

class Hello
{
  public:
    virtual void hello() const = 0; // 純虛函數
    virtual ~Hello() = default;
};

7. IO庫

頭文件iostream主要是cin、cout、cerr、clog。

頭文件fstream主要是fstream、ifstream、ofstream.

頭文件sstream主要是stringstream、istringstream、ostringstream。

IO對象不能被拷貝和賦值。

7.1 文件讀寫

fstream操作

fstream fstrm; // 未綁定文件
fstream fstrm(s); // 綁定文件s
fstream fstrm(s, mode); // 指定mode打開文件
fstrm.open(s, mode); // 指定mode打開文件
fstrm.close(); // 關閉文件
fstrm.is_open(); // 是否打開文件

fstream的mode

in // 讀模式
out // 寫模式
app // 追加模式
ate // 打開文件後定位到文件末尾
trunc // 截斷文件
binary // 以二進制進行IO

讀寫文件

ofstream out("hello.txt");
out<<"hello world"<<endl;
ifstream in("hello.txt");
string txt;
in>>txt;
cout<<txt<<endl;
out.close();
in.close();

stringstream操作

sstream strm; // 未綁定字符串
sstream strm(s); // 保存字符串的拷貝
strm.str(); // 返回strm的string拷貝
strm.str(s); // 保存字符串的拷貝

string流

string txt = "hello\nworld";
istringstream in(txt);
string tmp;
in>>tmp;
cout<<tmp<<endl;
in>>tmp;
cout<<tmp<<endl;
ostringstream out;
out<<"hello world"<<endl;
cout<<out.str();

8. 容器

8.1 容器類型

vector // 可變大小數組。支持快速隨機訪問,在尾部以外插入和刪除元素很慢
deque // 雙端隊列。支持快速隨機訪問,在頭尾位置插入和刪除很快。
list // 雙向鏈表。只支持雙向順序訪問。在任何位置插入和刪除很快。
forward_list // 單向鏈表。只支持單向順序訪問。在任何位置插入和刪除很快。
array // 固定大小數組。支持快速隨機訪問。不能添加和刪除元素。
string // 和vector類似。

8.2 容器構造函數

C c;              // 空容器
C c1(c2);         // 拷貝構造
C c1 = c2;        // 拷貝構造
C c(n);           // n個元素的容器
C c(n, val);      // n個val元素的容器
C c{a,b,c,d};     // 初始化列表
C c = {a,b,c,d};  // 初始化列表
C c(b,e)          // b和e迭代器範圍內的元素進行初始化

8.3 容器操作

c1.swap(c2);            // 容器交換元素。不影響之前的引用和迭代器
swap(c1, c2);

c.size();               // 容器大小
c.empty();              // 容器是否爲空
c.push_back(val);       // 在尾部添加元素
c.push_front(val);      // 在頭部添加元素
c.pop_back();           // 刪除尾部元素
c.pop_front();          // 刪除頭部元素
c.clear();              // 刪除所有元素
c.erase(iter);          // 刪除iter迭代器之前的元素
c.erase(b,e);           // 刪除b和e迭代器範圍內的元素
c.begin();              // 返回頭部迭代器
begin(c);            
c.end();                // 返回尾部迭代器
end(c);
c.insert(iter,val);     // 在iter迭代器的位置插入元素,返回插入位置的迭代器。
c.insert(iter,n,val);   // 在iter迭代器的位置插入n個val元素,返回插入位置的迭代器。
c.insert(iter,b,e);     // 在iter迭代器的位置插入b和e迭代器之前的元素,返回插入位置的迭代器
c.insert(iter,il);      // 在iter迭代器的位置插入初始化列表的元素。返回插入位置的迭代器。
c.emplace(iter,args);   // 在iter迭代器的位置使用args參數列表來構造元素並插入。返回插入位置的迭代器。
c.emplace_front(args);  // 在頭部使用args參數列表來構造元素並插入。
c.emplace_back(args);   // 在尾部使用args參數列表來構造元素並插入。
c.at(n)                 // 返回n位置元素的引用
c[n]                    // 返回n位置元素的引用
c.back()                // 返回尾部元素的引用
c.front()               // 返回頭部元素的引用 

注意:對容器進行插入刪除,可能會造成之前的迭代器失效。所以要使用返回的迭代器來更新操作前的迭代器。

vector<int> v1(10);
auto iter = begin(v1) + 2;
iter = v1.insert(iter, 100);
cout<<v1[2]<<endl;

8.4 容器適配器

容器適配器有棧適配器和隊列適配器

stack<int> a;
stack<int, vector<int>>a(v1);
queue<int> b;
queue<int, deque<int>>b(d1);

9. 泛型算法

在頭文件numeric和algorithm中。

9.1 容器求和

accumulate(v1.cbegin(), v1.cend(), 0);

9.2 容器元素比較

equal(v1.cbegin(), v1.cend(), v2.cbegin());

9.3 填充容器

fill(v1.begin(), v1.end(), 9);
fill_n(v1.begin(), 3, 8);

9.4 拷貝容器

int a[] = {1,2,4,5,6,7};
int b[sizeof(a) / sizeof(*a)];
copy(begin(a), end(a), b);

9.5 替換元素

replace(v1.begin(), v1.end(), 6, 99);
// v1元素不變,vec拷貝v1元素且替換6爲99
replace_copy(v1.cbegin(), v1.cend(), back_inserter(vec), 6, 99);

9.6 排序元素

sort(strs.begin(), strs.end());
sort(strs.begin(), strs.end(), [](const string &v1, const string &v2) -> bool {
  return v1.size() < v2.size();
});

9.7 去重元素

auto iter = unique(strs.begin(), strs.end());
strs.erase(iter, strs.end());

9.8 lambda表達式

[capture list](parameter list) -> return type { function body }

int sz = 2;
auto func = [sz](const string &str)
{
  return str.size() > sz;
};
cout << func("199") << endl;

lambda捕獲

值捕獲:[v]

引用捕獲:[&v]

隱式捕獲

[=]函數體捕獲的變量爲值捕獲

[&]函數體捕獲的變量爲引用捕獲

[=,&v1]v1爲引用捕獲,其他爲值捕獲

[&,v1]v1爲值捕獲,其他爲引用捕獲

9.9 查詢元素

auto iter = find(strs.cbegin(), strs.cend(), "aj");
int sz = 2;
auto iter = find_if(strs.begin(), strs.end(), [sz](const string &value) {
  return value.size() > sz;
});

9.10 遍歷元素

for_each(strs.cbegin(), strs.cend(), [](const string &value) {
  cout << value << " ";
});

9.11 bind函數

bind函數用於適配函數,使用佔位符和默認參數來生成一個函數, 在頭文件functional。

bool check(const string &str, string::size_type sz)
{
  return str.size() < sz;
}

auto check2 = bind(check, std::placeholders::_1, 5);
cout << check2("193") << endl;

其中_1爲返回的可調用函數的入參位置。

10. 關聯容器

map 關聯數組,保存關鍵字-值對
set 關鍵字即值,只保存關鍵字的容器
multimap 關鍵字可重複出現的map
unordered_map 用哈希函數組織的map
unordered_set 用哈希函數組織的set
unordered_multimap 哈希組織的map,關鍵字可以重複出現
unordered_multiset 哈希組織的set,關鍵字可以重複出現

它們在map、set、unordered_map、unordered_set頭文件。

map的元素是一個pair的鍵值對對象。first成員爲鍵,second成員爲值。

10.1 遍歷容器

map<string, string> authors = {{"xue", "19"}, {"op", "99"}};
for (auto &&pair : authors)
{
  cout << pair.first << " " << pair.second << endl;
}

10.2 添加元素

auto ret = authors.insert({"mk", "199"});
if (ret.second)
{
  cout << ret.first->first << " " << ret.first->second << endl;
}
authors.emplace("mk", string(10, 'c'));
authors.insert(b, e); // 添加b和e迭代器的範圍的pair
authors.insert(p, pair) // 在迭代器p位置添加pair
authors.insert({{"mk", "111"}, {"lo", "444"}}) // 添加初始化列表

插入的結果是一個pair對象,frist爲插入元素的迭代器,second爲插入是否成功。

10.3 刪除元素

authors.erase("mk"); // 指定關鍵字
authors.erase(iter); // 指定迭代器
authors.erase(b, e); // 指定迭代器b和e的範圍

10.4 下標訪問

下標訪問的元素不存在會插入新元素。at訪問會拋出異常。

map<string, int> mymap;
mymap["hello"] = 10; // 插入新元素
mymap.at("world") = 19; // 拋出異常

10.5 查詢元素

使用find或者count來查詢元素是否存在。

auto iter = mymap.find("hello");
if (iter != mymap.end())
{
  cout << iter->second << endl;
}
auto count = mymap.count("hello2");

multimap和multiset允許重複關鍵字,需要同時使用count和find來遍歷元素

multimap<string, int> my_multi_map = {{"hello", 1}, {"hello", 2}, {"hello2", 3}};
auto iter = my_multi_map.find("hello");
auto count = my_multi_map.count("hello");
while (count)
{
  cout << iter->second << endl;
  ++iter;
  --count;
}

使用lower_bound和upper_bound來獲取搜索的迭代器範圍

multimap<string, int> my_multi_map = {{"hello", 1}, {"hello", 2}, {"hello2", 3}};
for (auto begin = my_multi_map.lower_bound("hello"), end = my_multi_map.upper_bound("hello"); begin != end; begin++)
{
  cout << begin->second << endl;
}

使用equal_range獲取搜索的範圍

for (auto pos = my_multi_map.equal_range("hello"); pos.first != pos.second; pos.first++)
{
  cout << pos.first->second << endl;
}

10.6 無序容器

無序容器使用hash函數組織元素。無序容器的關鍵字類型需要實現==運算符和hasher函數。可以使用標準庫的hash模板來生成hash值。

auto hashCode = hash<string>()("123");

11. 動態指針

爲了方便使用動態內存,標準庫提供了兩種智能指針來管理動態對象。shared_ptr允許多個指針指向同一個對象;unique_ptr獨佔指向的對象。weak_ptr是弱引用,用來指向shared_ptr管理的對象。它們定義在memory頭文件。

11.1 shared_ptr

shared_ptr採用引用計數的方式管理動態內存。當存在n個shared_ptr指向同個對象,則對象的引用計數爲n,shared_ptr實例銷燬時,引用計數減1,所以實例銷燬,引用計數爲0;釋放對象內存。shared_ptr是一個對象,在析構函數管理引用計數。

shared_ptr<int> sp; // 空智能指針,可以指向類型爲T的對象。
unique_ptr<int> up;
p // 可以用來條件判斷。
p.get() // 獲取管理的指針
swap(p, q) // 交換智能指針
p.swap(q)

11.2 初始化shared_ptr

shared_ptr<int> sp = make_shared<int>(29);
shared_ptr<int> sp(new int(2024));
shared_ptr<T> sp(p, d); // pT類型指針,d爲回收T類型指針的函數。
sp.reset() // sp指向空,之前指向的對象的引用計數減1.
sp.reset(q) // 指向q的對象,q對象引用計數加1,之前指向的對象引用計數減1.
sp.reset(q, d) // 提供回收q類型指針的函數。默認是使用delete操作符。

不要混用普通指針和智能指針。

1. delete普通指針後,使用智能指針出錯。

2. 智能指針銷燬後,自動delete普通指針。使用普通指針出錯。

不要用get初始化另一個智能指針。

1. 智能指針銷燬後,自動delete指針,使用另一個智能指針出錯。

11.3 unique_ptr

unique_ptr不能被多個實例引用。

unique_ptr<int> up; // 指向空的unique_ptr
unique_ptr<int> up(new int(100));
unique_ptr<T, D> up(p, d); // 指向空。 d爲類型D的對象,用於刪除T類型
up.release(); // 返回指向的指針並指向空。
up.reset(q); // 指向q指針。

11.4 weak_ptr

week_ptr用來解決循環引用的問題,weak_ptr指向的shared_ptr指針,引用計數不會增加。

weak_ptr<int> wp = sp;
weak_ptr<int> wp(sp);
wp.reset(); // 指向空
wp.lock(); // 指向sp的引用計數爲0則返回空的shared_prt,否則返回指向的shared_ptr

11.5 動態數組

int *p = new int[10]; // 10個未初始化的int
int *p = new int[10](); // 10個初始化爲0的int
int *p = new int[10]{1,2,3,4} // 初始化爲1,2,3,4,後面全爲0
delete [] p;
p = nullptr;

shared_ptr需要提供delete函數來釋放動態數組,unique_ptr不需要提供。

shared_ptr<int>sp2(new int[10](), [](int *p) {delete [] p;});
unique_ptr<int[]>up2(new int[10]());

11.6 allocator

allocator將分配內存和初始化分離開來

allocator<string> a;      // 定義一個string的allocator對象
auto q = a.allocate(10);  // 分配10個字符串
a.construct(q, 10, '1'); // 構造第一個字符串
a.destroy(q); // 執行q的析構函數
a.deallocate(q, 10); // 釋放之前分配的10個字符串內存

11.7 allocator算法

allocator<int> my_alloc;
vector<int> numbers{1,2,3,4,5};
auto p1 = my_alloc.allocate(5);
uninitialized_copy(numbers.cbegin(), numbers.cend(), p1);
my_print(p1, 5); // 1,2,3,4,5
uninitialized_fill(p1, p1 + 5, 10);
my_print(p1, 5); // 10,10,10,10,10
uninitialized_copy_n(numbers.cbegin(), 5, p1);
my_print(p1, 5); // 1,2,3,4,5
uninitialized_fill_n(p1, 5, 10);
my_print(p1, 5); // 10,10,10,10,10

12. 模板與泛型編程

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章