C++知識點 —— 整合(持續更新中)

Fang-發表於2020-01-28

        本文記錄自己在自學C++過程中不同於C的一些知識點,適合於有C語言基礎的同學閱讀。如果紕漏,歡迎回復指正

目錄

第一部分 基礎知識

一、HelloWorld與名稱空間

二、引用和引用引數

2.1引用的定義

2.2 將引用用作函式引數

2.3 將引用用於類物件

2.4 引用和繼承

2.5 何時使用引用引數

2.6 引用和指標的區別

三、行內函數

四、預設引數的函式

4.1 預設引數的使用形式

4.2 預設引數的順序規定

五、函式過載(多型)

5.1 函式過載的定義

5.2 extern "C"

六、函式模板和類别範本

6.1 函式模板

6.1.1 一個型別引數的函式模板

6.1.2 多個型別引數的函式模板

6.1.3 函式模板進階

6.2 類别範本

6.2.1 單個類别範本

6.2.2 繼承中的類别範本

七、類、物件、封裝

7.1 概念

7.2 定義一個類

7.3 成員函式

7.3.1 成員函式的定義和使用:

7.3.2 this指標

7.3.3 物件做引數

7.4 封裝

八、建構函式

九、解構函式

十、標準庫型別string

10.1 字串物件的初始化

10.2 字串操作

十一、static資料成員和成員函式

11.1 static資料成員

11.2 static成員函式

十二、動態記憶體分配

十三、拷貝建構函式

13.1 呼叫時機

13.2 淺拷貝和深拷貝

淺拷貝

深拷貝

13.3 什麼時候需要定義拷貝建構函式

十四、const關鍵字

14.1 初始化列表

14.2 const 和指標

14.3 const成員變數

14.4 const成員函式(常成員函式)

14.5 const物件(常物件)

十五、友元函式和友元類

15.1 友元函式

15.2 友元類

15.3 設計模式——單例設計模式

15.4 Valgrind記憶體檢測工具

十六、運算子過載

16.1 成員函式版本

16.2 非成員函式版本

16.3 運算子過載的使用方法

16.4 運算子過載的限制

十七、繼承

17.1 類之間關係

17.2 繼承的意義

17.3 C++中繼承三種方式

十八、多型

18.1 虛擬函式與抽象類

18.2 純虛擬函式與介面類

十九、類别範本

二十、STL

20.1 Vector

20.2 Iterator

20.3 list

20.4 map

二十一、設計模式

21.1 觀察者模式

21.2 策略模式

第二部分 進階知識

一、過載<<操作符

二、捕獲異常

2.1 基本語法

2.2 函式的異常宣告列表

2.3 C++標準異常類

第三部分 其它零碎知識點


第一部分 基礎知識

一、HelloWorld與名稱空間

#include <iostream>
using namespace::std;
cout << "Hello World!" << endl;

namespace One {
    int M = 200;
    int inf = 10;
}
namespace Two {
    int x;
    int inf = 100;
}

int main(int argc, char *argv[])
{
    using Two::x;
    cout << x << endl;
    cout << One:inf << endl;
}

 

二、引用和引用引數

引用是別名,宣告時必須初始化。實際程式碼中多用作函式的形參,通過將引用變數用作引數,函式將使用原始資料,而不是其副本。

2.1引用的定義

int intOne;
int& rInt = intOne;
intOne = 5;

cout << rInt << endl;		// 5
rInt = 7;						//修改引用的值,也就是修改intOne
cout << intOne << endl;	// 7

2.2 將引用用作函式引數

void swap(int &x, int &y) {
    int tmp;
    tmp = x;
    x = y;
    y = tmp;
}
//呼叫:
int a = 10;
int b = 2;
swap(a, b);

 

2.3 將引用用於類物件

double price_count(const Pen &p1, const Pen &p2);	//形參使用指向類物件的引用
result = price_count(pen1, pen2);		//呼叫函式時傳入類物件即可,而不是指向類物件的引用

price_count函式的兩個形參都是const引用,可以接受const和非const Pen,函式內不能對實參進行修改,只能讀取。

 

2.4 引用和繼承

      基類引用可以指向派生類物件,而無需進行強制型別轉換。實際結果是:可以定義一個接受基類引用作為引數的函式,呼叫該函式時,可以將基類物件作為引數,也可以將派生類物件作為引數。

 

2.5 何時使用引用引數

      一般而言,傳遞類物件引數的標準方式就是按引用傳遞。

      陣列-------->只能使用指標

      結構-------->可以使用指標、引用

      類物件------>使用引用

 

2.6 引用和指標的區別

  1. 指標是變數,可以重新賦值指向別的變數(地址)
  2. 引用在定義時必須進行初始化,並且不能再關聯其它變數。
  3. 有空的指標,沒有空的引用:(void引用是不合法的)

void &a = 3;   //void本質上不是型別,沒有void的引用。

 

 

三、行內函數

在定義和宣告一個函式時加上inline,inline函式應該儘可能簡短,且不要做複雜的操作,如浮點運算等。

 

四、預設引數的函式

4.1 預設引數的使用形式

只需在宣告函式時使用如下的形式來指定預設值(定義時不用指定)

int add(int x = 5, int y = 6, int z = 3);
int main()
{
	add();	//三個引數都使用預設值,結果是14
	add(1,5);	//第三個引數使用預設值
	add(1,2,3);	//不適用預設值
}

 

4.2 預設引數的順序規定

預設引數從右到左逐漸定義;呼叫函式時,也只能從右到左匹配預設引數(即從左到右使用自定義的值):

void func(int a=1, int b, int c=3, int d=4);	//error
void func(int a, int b=2, int c=3, int d=4);	//ok

對於第二個函式宣告,其呼叫的方法規定為:

func(10,20);			//ok: c、d使用預設值
func(10,20,30,40);	//ok: 不適用預設值
func();				//error: a沒有預設值,必須給定一個值
func(10,20,,40);	//error: 只能從右到左匹配預設引數

 

五、函式過載(多型)

5.1 函式過載的定義

兩個及以上的函式,函式名相同,形參的數目或型別不同,編譯器根據引數的型別和個數自動匹配並呼叫,即函式過載。

int abs(int);
long abs(long);
double abs(double);

注意:

  • 編譯器將型別引用和型別本身是為同一特徵標,所以下面兩個函式原型不能過載:
double cube(double x);
double cube(double &x);
  • 形引數目和型別相同,只有函式型別不同時,則不可以對函式進行過載。

 

5.2 extern "C"

extern "C"的主要作用就是為了能夠正確實現C++程式碼呼叫其他C語言程式碼。加上extern "C"後,會指示編譯器這部分程式碼按C語言的進行編譯,而不是C++的。

由於C++支援函式過載,因此編譯器編譯函式的過程中會將函式的引數型別也加到編譯後的程式碼中,而不僅僅是函式名;而C語言並不支援函式過載,因此編譯C語言程式碼的函式時不會帶上函式的引數型別,一般只包括函式名。

#ifdef __cplusplus
extern "C" {
#endif

int foo(char, int);

#ifdef __cplusplus
}
#endif

 

六、函式模板和類别範本

在 C++ 中,模板分為函式模板和類别範本兩種。函式模板是用於生成函式的,類别範本則是用於生成類的。

6.1 函式模板

      函式模板使用泛型來定義函式,其中的泛型可以用具體的型別(如int或double)替換,因此有時也稱為通用程式設計。

 

 

 

6.1.1 一個型別引數的函式模板

template<typename T>
void Swap(T &x, T &y)
{
	T tmp = x;
	x = y;
	y = tmp;
}

      typename可以用class替換。

int main(int argc, char *argv[])
{
//函式模板
	int a = -10;
	int b = 23;
	double c = -12.34;
	double d = 56.789;

	cout << "Swap(a, b) = " << Swap(a, b) << endl;
	cout << "Swap(c, d) = " << Swap(c, d) << endl;
}

      編譯器在編譯到Swap(a, b);時找不到函式 Swap 的定義,但是發現實參a、b都是int型別的,用int型別替換Swap模板中的T能得到下面的模板函式

void Swap(int &x, int &y)
{
	int tmp = x;
	x = y;
	y = tmp;
}

      同理,編譯器在編譯到Swap(c, d)時會生成模板函式

void Swap(double &x, double &y)
{
	double tmp = x;
	x = y;
	y = tmp;
}

 

6.1.2 多個型別引數的函式模板

      函式模板也可以有多個型別的引數:

template<typename T1, typename T2>
T2 print(T1 arg1, T2 arg2)
{
	cout << arg1 << “ ” << arg2 << endl;
	return arg1;
}

 

6.1.3 函式模板進階

當函式模板遇上過載和普通函式:

函式模板不允許自動型別轉換,普通函式則可以;

編譯器會優先選擇普通函式,如果函式模板能產生更好的引數型別匹配,則選擇函式模板;函式模板也可以過載。

可以通過空模板實參列表的語法限定編譯器只通過模板匹配:Swap<>(a, b);

 

 

6.2 類别範本

 

6.2.1 單個類别範本

template<typename T>
class ClassModule {
public:
	ClassModule(T n) {
		this->num = n;
	}

	T getnum() {
		return num;
	}

private:
	T num;
}

void main()
{
	ClassModule<int> myclass(100);	//使用類别範本需要用引數列表指定具體的引數型別
	cout << myclass.getnum() << endl;
	return;
}

 

6.2.2 繼承中的類别範本

      當子類從模板類繼承時,需要讓編譯器指定基類的具體資料型別,即需要指定基類的引數型別:class B: public A<int>

//繼承中的類别範本
template<typename T>
class A {
public:
	A(T n) {
		this->t = n;
	}
	void printA() {
		cout << "A:" << t << endl;
	}
public:
	T t;
};
class B : public A<int> {
public:
	B(int m) : A(m){};	//使用基類的建構函式
	void printB() {
		cout << "B:" << t << endl;
	}
};
int main(int argc, char *argv[])
{
	B b(88);
	b.printA();		//88
	b.printB();		//88
	return 0;
}

 

 

七、類、物件、封裝

7.1 概念

類是建立物件的模板

類是物件的定義,物件是類的例項化,物件的產生離不開類這個模板

物件的三大特性:行為、狀態、標識

 

7.2 定義一個類

class Car {
public:
void run() {		//公有函式
...
}
void stop() {
...
}

protected:

private:
int price;	//私有資料成員
int carNum;
};

 

在類中定義成員函式:

類中定義的成員函式一般為行內函數,即使沒有明確用inline標識

類定義通常放在標頭檔案中

在類之後定義成員函式:

將類定義和其成員函式定義分開

類定義(標頭檔案)是類的外部介面,類的成員函式定義是類的內部實現

 

7.3 成員函式

7.3.1 成員函式的定義和使用:

類成員函式的過載:

類成員函式可以像普通函式一樣過載,但不同類的同名函式不算過載

 

類成員函式的預設引數:

類成員函式可以像普通函式一樣使用預設引數

 

成員函式一般在.cpp中實現,在物件定義時只提供一個介面供外部使用:

car.h:

class Car {
public:
void run();
private:
int price;
int carnum;
}
-------------------------------------------------------------------------
car.cpp:

void Car::run() {
cout << "Car run..." << endl;
}
-------------------------------------------------------------------------
main.cpp:

int main() {
Car a;
a.run();
}

 

7.3.2 this指標

等於物件的地址

 

7.3.3 物件做引數

 

 

7.4 封裝

OOP三大特性:封裝、繼承、多臺

定義類,定義其資料成員、成員函式的過程陳為封裝。

 

類的訪問修飾符:

public:          類本身(即成員函式)、子類、物件

protected:    類本身(即成員函式)、子類

private:         類本身(即成員函式)

 

八、建構函式

完成物件初始化的函式,是特殊的成員函式。

建構函式名和類名相同,且無返回型別(不是void型)。

class Car {
public:
Car(int pri=100000, int num=0);
int setProperty(int pri, int num);
private:
int price;
int carnum;
}

Car::Car() {
setProperty(int pri, int num);
}

 

 

對於一個類,如果定義了帶引數的建構函式,並且不是所有的形參都有預設值,那麼就必須定義一個不帶引數的建構函式,否則當呼叫不帶引數的建構函式時會報錯。例如:

class Person {
public:
	Person(const string & name, const int age) : m_name(name), m_age(age);
private:
string m_name;
	int m_age;
}

類定義如上,

1、如果則使用Person person01;建立物件時會報錯,必須定義一個不帶引數的建構函式:Person(){ };

2、如果建構函式第一個形參name有預設值,則使用Person person01;建立物件時不會報錯,此時會呼叫帶引數的建構函式。並且,此時也不能再定義一個不帶引數的建構函式,否則當使用Person person01;建立物件時,編譯器會同時匹配上兩個建構函式,會報類似“warning C4520: “Person”: 指定了多個預設建構函式”這樣的錯誤。

 

 

九、解構函式

完成釋放物件空間的函式,是特殊的成員函式。

解構函式名為類名前加“~”,且無形參、無返回型別(不是void型)。

解構函式不能隨意呼叫,也不能過載,只是在類物件生命週期結束的時候,由系統自動呼叫

解構函式都是虛擬函式,基類的析構器應寫成虛擬函式,否則析構器只會呼叫基類的析構器。

 

十、標準庫型別string

10.1 字串物件的初始化

初始化方法

說明

string s1;

預設建構函式,s1為空字串

string s2(s1);

將s2初始化為s1的一個副本

string s3("value");

將s3初始化為字串的副本

string s4(n, 'c');

將s4初始化為n個c的字串

string s5 = "abcd";

 

string s6 = {"abcd"};

 

 

10.2 字串操作

string操作

說明

s.empty()

字串為空則返回true,否則返回false

s.size()

返回長度

s[n]

返回第n個字元

s1+s2

兩個字串連線

s1=s2

s1的內容替換為s2

s1==s2

相等則返回ture,否則返回false

!=,<,<=,>,>=

保持這些操作符慣有的含義

 

 

十一、static資料成員和成員函式

static修飾類中成員,表示類的共享資料

11.1 static資料成員

static資料成員不像普通的類資料成員,static類資料成員獨立於一切類物件。static類資料成員是與類關聯的,但不與該類定義的物件有任何關係。即static不會像普通類資料成員一樣每一個類物件都有一份,全部類物件是共享一個static類成員的。static 成員變數不佔用物件的記憶體,而是在所有物件之外開闢記憶體,即使不建立物件也可以訪問

例如A類物件修改了static成員為1,那麼B物件對應的static類物件成員的值也會是1。

使用static成員變數必須在類外進行初始化。靜態成員變數在初始化時不能再加 static,但必須要有資料型別。被 private、protected、public 修飾的靜態成員變數都可以用這種方式初始化:

int Martain::martain_count = 2;

 

使用static資料成員的好處:

用static修飾的成員變數在物件中是不佔記憶體的,因為他不是跟物件一起在堆或者棧中生成,用static修飾的變數在靜態儲存區生成的。所以用static修飾一方面的好處是可以節省物件的記憶體空間。如同你建立100個Person物件,而這100個物件都有共有的一個變數,例如叫國籍變數,就是Person物件的國籍都是相同的。

那如果國籍變數用static修飾的話,即使有100個Person物件,也不會建立100個國籍變數,只需要有一個static修飾的國籍變數就可以了。這100個物件要用的時候,就會去呼叫static修飾的國籍變數。否則有100個Person變數,就會建立100個國籍變數,在國籍變數都是相同的情況下,就等於浪費空間了。

 

11.2 static成員函式

由於static修飾的類成員屬於類,不屬於物件,因此static類成員函式是沒有this指標的(this指標是指向本物件的指標)。正因為沒有this指標,所以static類成員函式不能訪問非static的類成員,只能訪問 static修飾的靜態類成員

 

普通成員函式可以通過物件名進行呼叫,而static成員函式必須通過類名進行呼叫(因為它與類關聯):

class Martain {
plubic:
int func1();
static int func2();
}

main(){
Martain m1;
m1.func1();
Martain::func2();
}

注:本節其它內容見測試程式碼"004-static"

 

 

十二、動態記憶體分配

new/delete是運算子(關鍵字),malloc/free是函式呼叫。

int *p1 = new int;
int *p2 = new int[10];	//分配10個int型的記憶體空間
Student *pstu1 = new Student;	//分配一個Student類的記憶體空間
Student *pstu2 = new Student();	//這兩種用法都是可以的
delete p1;
delete[] p2;

 

 

十三、拷貝建構函式

Student(const Student& s);	//拷貝建構函式的固定形式

 

13.1 呼叫時機

1、定義一個新物件並用一個同型別的物件進行初始化時

2、物件作為實參或函式返回物件時

 

例項程式碼:

Student s1("Jame");
printf("&s1 = %x\n", &s1);

//呼叫時機1:用一個物件初始化另一個物件
Student s2 = s1;
printf("&s2 = %x\n", &s2);
//呼叫時機2:物件作為實參或函式返回物件
foo(s1);

Student s4 = zoo();
printf("&s4 = %x\n", &s4);

 

13.2 淺拷貝和深拷貝

淺拷貝

如果不定義拷貝建構函式,則編譯器會使用預設拷貝建構函式,是淺拷貝,淺拷貝過程如下:

淺拷貝遇到含有指標的物件型別時很容易出問題,因為淺拷貝出來的物件和原物件的指標都指向同一個記憶體空間,當其中一個物件被析構後,另一個物件再使用時就會發生記憶體溢位。

 

深拷貝

深拷貝遇到指標時,會重新分配一塊空間,因此不會有問題。

 

13.3 什麼時候需要定義拷貝建構函式

1、類資料成員有指標

2、類資料成員管理資源(如開啟一個檔案)

3、一個類需要解構函式釋放資源時,那它也需要一個拷貝建構函式

 

某些情況下想禁止呼叫拷貝建構函式、賦值運算子,那麼可以將這兩個函式放到private中。如下:

class Uncopyable {
private:
	Uncopyable(const Uncopyable &);	//阻止拷貝構造
	Uncopyable &operator=(const Uncopyable &);	//阻止賦值運算子
};

 

 

十四、const關鍵字

14.1 初始化列表

建構函式的初始化列表

初始化 const 成員變數的唯一方法就是使用初始化列表

 

14.2 const 和指標

const 也可以和指標變數一起使用,這樣可以限制指標變數本身,也可以限制指標指向的資料。const 和指標一起使用會有幾種不同的順序,如下所示:

1. const int *p1;

2. int const *p2;

3. int * const p3;

在最後一種情況下,指標是隻讀的,也就是 p3 本身的值不能被修改;在前面兩種情況下,指標所指向的資料是隻讀的,也就是 p1、p2 本身的值可以修改(指向不同的資料),但它們指向的資料不能被修改。

當然,指標本身和它指向的資料都有可能是隻讀的,下面的兩種寫法能夠做到這一點:

1. const int * const p4;

2. int const * const p5;

const 和指標結合的寫法——記憶:如果變數名和*號被分隔開,則const修飾的是[變數名](即指標),指標只讀;如果沒有被分割開,則修飾的是[*變數名](即指標指向的變數),也就是指標指向的變數只讀。

 

14.3 const成員變數

初始化const成員變數的唯一方法是使用初始化列表(不能在建構函式的函式體中初始化)。

class Demo {
private:
	const int m_len;
int *m_arr;
public:
	Demo(int len) : m_len(len) {	//通過初始化列表初始化const成員變數
		m_arr = new int[len];
};
}

class Demo {
private:
	const int m_len;
int *m_arr;
public:
	Demo(int len){};
}
Demo:Demo(int len) {
	m_len = len;	//錯誤:不能在函式體內初始化
m_arr = new int[len];
}

 

14.4 const成員函式(常成員函式)

const 成員函式可以使用類中的所有成員變數,但是不能修改它們的值,這種措施主要還是為了保護資料而設定的。const 成員函式也稱為常成員函式。

常成員函式需要在宣告和定義的時候在函式頭部的結尾加上 const 關鍵字

 

14.5 const物件(常物件)

在 C++ 中,const 也可以用來修飾物件,稱為常物件。一旦將物件定義為常物件之後,就只能呼叫類的 const 成員(包括 const 成員變數和 const 成員函式)了

 

定義常物件的語法和定義常量的語法類似:

const classnameobject(params);
classnameconst object(params);

兩種方式定義出來的物件都是常物件。

 

當然也可以定義 const 指標:

const classname *p = new classname (params);
classname const *p = new classname (params);

classname為類名,object為物件名,params為實參列表,p為指標名。

 

 

十五、友元函式和友元類

 

15.1 友元函式

待補充

15.2 友元類

待補充

15.3 設計模式——單例設計模式

待補充

15.4 Valgrind記憶體檢測工具

待補充

 

 

十六、運算子過載

運算子函式的格式:operator op(argument-list);

      運算子過載可以選擇使用成員函式或非成員函式來實現。

16.1 成員函式版本

Time operator+(const Time & t) const;

 

16.2 非成員函式版本

      非成員函式應是友元函式,這樣才可以直接訪問類的私有資料。

friend Time operator+(const Time & t1, const Time & t2);

      注意:友元函式在定義時不要用關鍵字friend,在宣告時採用。

 

16.3 運算子過載的使用方法

total = coding.operator+(fixing);
total = coding + fixing;

      上述兩種表示法都將呼叫operator+()方法。

 

      另外,也可以這樣用:

total = coding + fixing + morefixing;

      解釋:由於+是從左向右結合的運算子,所以會先被轉換成total = coding.operator+(fixing + morefixing);,然後函式引數被轉換成一個函式呼叫,結果就是:total = coding.operator+( fixing.operator+(morefixing) );

 

16.4 運算子過載的限制

1、過載後的運算子必須至少有一個運算元是使用者定義的型別,即不能將加法運算子過載成兩個int型相加,這樣限制是為了安全考慮;

2、不能違反運算子原來的句法規則,如不能修改運算子的優先順序;

3、不能建立新的運算子;

4、不能過載:sizeof  .  .*  ::  ?:  typeid  const_cast  synamic_cast  reinterpret_cast  static_cast;

5、大多數運算子都可以通過成員或非成員函式進行過載,但是這些運算子只能通過成員函式進行過載:=  ()  []  ->

 

十七、繼承

OOP三大特性

封裝  繼承  多型

 

17.1 類之間關係

is-a繼承體現

has-a組合體現

 

17.2 繼承的意義

程式碼重用

體現不同抽象層次

 

17.3 C++中繼承三種方式

公有繼承

私有繼承

多重繼承

 

公有繼承形式:

class Teacher: public Person {

 

}

 

 

UML  astah

 

父類          子類

基類          繼承類

 

 

子類只能訪問父類的public和protected成員,不能訪問private成員。

在構造一個子類時,父類部分由父類的建構函式完成,子類的部分由子類的建構函式完成。

構造一個子類時,先構造父類,然後構造子類,析構時相反。

 

 

十八、多型

 

OOP三大特性:封裝、繼承、多型

多型:同樣的方法呼叫而執行不同操作、執行不同程式碼。

LSP(里氏代換原則):子型別必須能夠替換它們的基類。

 

18.1 虛擬函式與抽象類

虛擬函式 是在基類中使用關鍵字 virtual 宣告的函式。在派生類中重新定義基類中定義的虛擬函式時,會告訴編譯器不要靜態連結到該函式。

虛擬函式是繼承的,基類中宣告為虛擬函式的函式,在子類中不可能再設定為非虛擬函式。

我們想要的是在程式中任意點可以根據所呼叫的物件型別來選擇呼叫的函式,這種操作被稱為動態連結,或後期繫結

      當一個類中含有虛擬函式時,這個類叫抽象類

Class Animal {
	virtual ~Animal();
	virtual void makeSound();
}

擴充套件:

class Pet {
	eat();
}
class Dog : public Pet {
	eat();
}
int main() {
	Pet * p = new Dog;	//如果指標為Pet型別,則eat必須為虛方法,如果是Dog型別,則不需要。
	p->eat();
}

p->eat()這裡會呼叫Pet的eat方法,而不是Dog的。為了使編譯器正確地連結到Dog的eat方法,需要在基類的eat()前加上Virtural保留字。

 

18.2 純虛擬函式與介面類

您可能想要在基類中定義虛擬函式,以便在派生類中重新定義該函式更好地適用於物件,但是您在基類中又不能對虛擬函式給出有意義的實現,這個時候就會用到純虛擬函式

在虛擬函式後加上=0就是純虛擬函式,純虛擬函式沒有主體

Class Animal {
	virtual ~Animal() = 0;
	virtual void makeSound() = 0;
}

 

上面就是一個介面類,介面類不能例項化,不能生成物件例項。

 

必須為多型基類宣告virtual解構函式,否則只會析構基類物件,不會析構子類物件。

 

編譯時的多型性:通過過載實現

執行時的多型性:通過虛擬函式實現

 

 

 

十九、類别範本

19.1

 

 

 

 

二十、STL

      STL由一些集合類以及在這些資料集合上操作的演算法構成,包括:容器、迭代器、演算法、函式物件。

  1. 容器(Container):管理某類物件的集合
  2. 迭代器(Iterator):在物件集合上進行遍歷
  3. 演算法(Algorithm):處理集合內的元素

在C++標準中,STL包括這些標頭檔案:<algorithm> <vector> <list> <map> <set> <memory> <functional> 等。

 

容器的類別:

  1. 序列式容器:vector、
  2. 關聯式容器

 

20.1 Vector

      動態陣列,自動分配記憶體

      API:push_back 、pop_back、begin、end、capacity、size 

      遍歷方法:iterator、下標法

 

20.2 Iterator

迭代器是物件導向版本的指標,迭代器可以指向容器中的一個位置,用來遍歷STL容器。

 

所有容器都提供兩種迭代器:

    1)Container::iterator           以讀寫模式遍歷元素

vector<int>::iterator iter;		//定義一個迭代器
*iter = 10;		//ok

    2)Container::const_interator           以只讀模式遍歷元素

vector<int>::const_iterator iter;		//定義一個只讀的迭代器
*iter = 10;		//error

    API:begin、end、

 

20.3 list

使用雙向連結串列管理元素,時順序訪問的容器。

遍歷方法:iterator

API:push_back、push_front、pop_back、begin、end、erase、size、sort

 

20.4 map

集合和對映時兩種主要的非線性容器類,內部實現一般為平衡二叉樹。

map是關聯容器。

遍歷方法:iterator

API:insert、begin、end

示例:

map<int,string> mymap;
//插入元素的方法:
mymap.insert(pair<int, string>(1,"one"));
mymap.insert(make_pair(2, "two"));
mymap.insert(map<int, string>::value_type(9, "nine"));
mymap[0] = "Zero";

//訪問方法1(map的迭代器):
map<int, string>::iterator iter_map;
for (iter_map = mymap.begin(); iter_map != mymap.end(); ++iter_map)
{
	cout << iter_map->first << " " << iter_map->second << endl;
}
//訪問方法2(map下標):
cout << mymap[0] << endl;

 

二十一、設計模式

物件導向設計的第一原則:針對介面程式設計,而不是針對實現程式設計。

物件導向設計的第二原則:優先使用物件組合,而不是類繼承。

繼承和物件組合常一起使用。

 

21.1 觀察者模式

      觀察者模式定義了物件間的一對多依賴關係,當一方的物件改變狀態時,所有的依賴者都會被通知並自動被更新。被依賴的一方叫目標或主題(Subject),依賴方叫觀察者(Observers)

      觀察者模式也叫“釋出-訂閱模式”(Publish-Subscribe)

 

 

 

 

 

21.2 策略模式

 

 

 

 

 

 

 

 

 

 

 

 

 

 

第二部分 進階知識

一、過載<<操作符

operator<<()函式的原型:

std::ostream& operator<<(std::ostream &os, Score s);

第一個引數:是將要向它寫入資料的那個流,以引用引數傳遞;

第二個引數:是要寫入的資料。不用的operator<<()過載函式就是因為這個引數才相互區別的;

返回值:一般和第一個引數一樣即可,返回寫入的那個流。

class Score {
public:
	Score(unsigned int score) : m_score(score) {};
	friend std::ostream& operator<<(std::ostream & os, Score &s);
private:
	unsigned int m_score;
};

std::ostream& operator<<(std::ostream & os, Score &s);
std::ostream& operator<<(std::ostream & os, Score &s)
{
	os << s.m_score;
	return os;
}

int main(int argc, char *argv[])
{ 
	Score Fang(80);
	cout << "Score:" << Fang << endl;

	return 0;
}

 

二、捕獲異常

2.1 基本語法

int exception_func(int num) 
{
    if (num == 0) {
		cout << "before throw -1 " << endl;
		throw -1;
		cout << "after throw -1 " << endl;
	}
	else if (num == 1) {
		throw -1.1;
	}
	else if (num == 2) {
		throw string("a string exception");
	}
	else {
		cout << "num = " << num << endl;
	}
	return num;
}
int main(int argc, char *argv[])
{
	//捕獲異常
	try {
		exception_func(2);
	}
	catch (int i) {
		cout << "capture a exception: " << i << endl;
	}
	catch (double d) {
		cout << "capture a exception: " << d << endl;
	}
	catch (string s) {
		cout << "capture a exception: " << s << endl;
	}
	catch (...) {
		cout << "capture a unknown exception. " << endl;
	}
}

      使用異常的原則是:應該只用來處理確實可能不正常的情況。

      構造器、析構器不應該使用異常。

      如果try語句塊無法找到與之匹配的catch語句塊,則它丟擲的異常將中止程式的執行。

如果使用物件作為異常,以“值傳遞”方式丟擲物件,以“引用傳遞”方式捕獲物件。

 

2.2 函式的異常宣告列表

為了增強程式的可讀性和可維護性,使程式設計師在使用一個函式時就能看出這個函式可能會丟擲哪些異常,C++ 允許在函式宣告和定義時,加上它所能丟擲的異常的列表,具體寫法如下:

void func() throw (int, double, A, B, C);

void func() throw (int, double, A, B, C){...}

上面的寫法表明 func 可能丟擲 int 型、double 型以及 A、B、C 三種型別的異常。異常宣告列表可以在函式宣告時寫,也可以在函式定義時寫。如果兩處都寫,則兩處應一致

 

如果異常宣告列表如下編寫,則說明 func 函式不會丟擲任何異常

void func() throw ();

一個函式如果不交待能丟擲哪些型別的異常,就可以丟擲任何型別的異常

 

函式如果丟擲了其異常宣告列表中沒有的異常,在編譯時不會引發錯誤,但在執行時, Dev C++ 編譯出來的程式會出錯;用 Visual Studio 2010 編譯出來的程式則不會出錯,異常宣告列表不起實際作用。

 

2.3 C++標準異常類

C++ 標準庫中有一些類代表異常,這些類都是從 exception 類派生而來的:

      示例:

int main(int argc, char *argv[])
{
try {
		vector<int> vec_int(10);
		vec_int.at(11) = 1;			//會丟擲異常
		//vec_int.assign(11, 1);	//不會丟擲異常
	}
	catch (out_of_range & e) {
		cout << e.what() << endl;
	}
	catch (...) {
		cout << "capture a unknown exception2. " << endl;
	}

	try {
		char * p = new char[0x7fffffff];
	}
	catch (bad_alloc & e) {
		cout << e.what() << endl;
	}
	catch (...) {
		cout << "capture a unknown exception3. " << endl;
	}
}

 

 

 

 

 

第三部分 其它零碎知識點

  1. const引用引數和臨時變數

僅當函式形參為const引用時,才允許生成臨時變數。分兩種情況:

  1. 實參的型別正確,但不是左值,例如(x+0.3)就不是左值;
  2. 實參的型別不正確,但可以轉換成正確的型別。

 

  1. 定義位於類宣告中的函式將自動成為行內函數。
  2. 行內函數要求在每個使用它的檔案中都對其進行定義,可以將內聯定義放在定義類的標頭檔案中。
  3. 變數作用域
  • 當區域性變數被定義時,系統不會對其初始化,您必須自行對其初始化。
  • 在程式中,區域性變數和全域性變數的名稱可以相同,但是在函式內,區域性變數的值會覆蓋全域性變數的值。
  • 在一個函式體內可以存在重名的變數,前提是它們的作用域不同:

#include <iostream>

using namespace std;

int main()

{

    int b = 2;

    {

        int b = 1;

        cout << "b = " << b << endl;   // 1

    }

    cout << "b = " << b << endl;          // 2

}

  • 儲存在靜態資料區的變數會在程式剛開始執行時就完成初始化,也是唯一的一次初始化。共有兩種變數儲存在靜態儲存區:全域性變數和 static 變數。
  • 全域性變數和和區域性變數同名時,可通過域名在函式中引用到全域性變數,不加域名解析則引用區域性變數

#include<iostream>

using namespace std;

int a = 10;

int main()

{

    int a = 20;

    cout << ::a << endl;   // 10

    cout << a << endl;     // 20

    return 0;

}

  • 在 VS2013 環境,對全域性變數的引用以及重新賦值,直接用全域性變數名會出現:count 變數不明確的問題。

在變數名前加上 :: 符號即可。

#include <iostream>

using namespace std;

 

int count = 10; //全域性變數初始化

int main()

{

    ::count = 1; //全域性變數重新賦值

    for (;::count <= 10; ++::count)

    {

        cout <<"全域性變數count="<< ::count << endl;

    }

    return 0;

}

  • 變數在記憶體中的位置

(1)程式碼區(text segment):又稱只讀區。通常是指用來存放程式執行程式碼的一塊記憶體區域。這部分割槽域的大小在程式執行前就已經確定,並且記憶體區域通常屬於只讀,某些架構也允許程式碼段為可寫,即允許修改程式。在程式碼段中,也有可能包含一些只讀的常數變數,比如字串常量等。

(2)全域性初始化資料區/靜態資料區(Data Segment):用來存放程式已經初始化的全域性變數,已經初始化的靜態變數。位置位於可執行程式碼段後面,可以是不相連的。在程式執行之初就為資料段申請了空間,程式退出的時候釋放空間,其生命週期是整個程式的執行時期。

(3)未初始化資料區(BSS):用來存放程式中未初始化的全域性變數和靜態變數,位置在資料段之後,可以不相連。其生命週期和資料段一樣。(2)和(3)統稱為靜態儲存區。

(4)棧區(Stack):又稱堆疊,存放程式臨時建立的區域性變數,如函式的引數值、返回值、區域性變數等。也就是我們函式括弧{}中定義的變數(但不包括static宣告的靜態變數,static意味著在資料段中存放的變數)。除此之外,在函式被呼叫時,其引數也會被壓入發起呼叫的程式棧中,並且等到呼叫結束後,函式的返回值也會被存放回棧中。編譯器自動分配釋放,是向下有限擴充套件的。

(5)堆區(Heap):位於棧區的下面,是向上有限擴充套件的。用於存放程式執行中動態分配的記憶體段,它的大小並不固定,可動態擴張或縮減。當程式呼叫malloc等函式分配記憶體的時候,新分配的記憶體就被動態新增到堆上(堆被擴張);當利用free等函式釋放記憶體的時候,被釋放的記憶體從堆中被剔除(堆被縮減)。一般由程式設計師進行分配和釋放,若不釋放,在程式結束的時候,由OS負責回收。

 

const修飾的全域性變數儲存在程式碼區中,const修飾的區域性變數儲存在棧段中。

 

 

 

 

 

 

相關文章