前言
C++提供了函數模板(functiontemplate)。所謂函數模板,實際上是建立一個通用函數,其函數類型和形參類型不具體指定,用一個虛擬的類型來代表。這個通用函數就稱為函數模板。凡是函數體相同的函數都可以用這個模板來代替,不必定義多個函數,只需在模板中定義一次即可。在調用函數時系統會根據實參的類型來取代模板中的虛擬類型,從而實現了不同函數的功能。
1)C++提供兩種模板機制:函數模板、類模板
2)類屬 —— 類型參數化,又稱參數模板
使得程序(算法)可以從邏輯功能上抽象,把被處理的對象(數據)類型作為參數傳遞。
總結:
? 模板把函數或類要處理的數據類型參數化,表現為參數的多態性,稱為類屬。
? 模板用於表達邏輯結構相同,但具體數據元素類型不同的數據對象的通用行為。
需求:寫n個函數,交換char類型、int類型、double類型變量的值。
案例:
#include
using namespace std;
/*
void myswap(int &a, int &b)
{
int t = a;
a = b;
b = t;
}
void myswap(char &a, char &b)
{
char t = a;
a = b;
b = t;
}
*/
//template 關鍵字告訴C++編譯器 我要開始泛型了.你不要隨便報錯
//數據類型T 參數化數據類型
template
void myswap(T &a, T &b)
{
T t;
t = a;
a = b;
b = t;
}
void main()
{
//char a = 'c';
int x = 1;
int y = 2;
myswap(x, y); //自動數據類型 推導的方式
float a = 2.0;
float b = 3.0;
myswap(a, b); //自動數據類型 推導的方式
myswap
cout<<"hello..."<
system("pause");
return ;
}
函數模板定義形式
template < 類型形式參數表>
類型形式參數的形式為:
typenameT1 , typenameT2 , …… , typename Tn
或 class T1 , class T2 , …… , classTn
函數模板調用
myswap
myswap(a, b); //自動數據類型推導
#include using namespace std; template void sortArray(T *a, T2 num) { Ttmp ; inti, j ; for(i=0; i { for(j=i+1; j { if(a[i] < a[j]) { tmp= a[i]; a[i]= a[j]; a[j]= tmp; } } } } template void pirntArray(T *a, int num) { inti = 0; for(i=0; i { cout< } } void main() { intnum = 0; chara[] = "ddadeeettttt"; num= strlen(a); printf("排序之前\n"); pirntArray(a,num); sortArray(a, num); //顯示類型調用模板函數 <> printf("排序之後\n"); pirntArray(a,num); cout<<"hello..."< system("pause"); return; }
函數模板和普通函數區別結論:
/*
函數模板不允許自動類型轉化
普通函數能夠進行自動類型轉換
*/
函數模板和普通函數在一起,調用規則:
/*
1函數模板可以像普通函數一樣被重載
2C++編譯器優先考慮普通函數
3如果函數模板可以產生一個更好的匹配,那麼選擇模板
4可以通過空模板實參列表的語法限定編譯器只通過模板匹配
*/
案例1:
#include
using namespace std;
template
void myswap(T &a, T &b)
{
T t;
t = a;
a = b;
b = t;
cout<<"myswap 模板函數do"<
}
void myswap(char &a, int &b)
{
int t;
t = a;
a = b;
b = t;
cout<<"myswap 普通函數do"<
}
void main()
{
char cData = 'a';
int iData = 2;
//myswap
myswap(cData, iData);
//myswap(iData, cData);
cout<<"hello..."<
system("pause");
return ;
}
案例2:
#include "iostream"
using namespace std;
int Max(int a, int b)
{
cout<<"int Max(int a, int b)"<
return a > b ? a : b;
}
template
T Max(T a, T b)
{
cout<<"T Max(T a, T b)"<
return a > b ? a : b;
}
template
T Max(T a, T b, T c)
{
cout<<"T Max(T a, T b, T c)"<
return Max(Max(a, b), c);
}
void main()
{
int a = 1;
int b = 2;
cout<
cout<
cout<
cout<
cout<
system("pause");
return ;
}
思考:為什麼函數模板可以和函數重載放在一塊。C++編譯器是如何提供函數模板機制的?
什麼是gcc
gcc(GNU C Compiler)編譯器的作者是Richard Stallman,也是GNU項目的奠基者。
什麼是gcc:gcc是GNU Compiler Collection的縮寫。最初是作為C語言的編譯器(GNU C Compiler),現在已經支持多種語言了,如C、C++、Java、Pascal、Ada、COBOL語言等。
gcc支持多種硬件平台,甚至對Don Knuth 設計的 MMIX 這類不常見的計算機都提供了完善的支持
gcc主要特征
1)gcc是一個可移植的編譯器,支持多種硬件平台
2)gcc不僅僅是個本地編譯器,它還能跨平台交叉編譯。
3)gcc有多種語言前端,用於解析不同的語言。
4)gcc是按模塊化設計的,可以加入新語言和新CPU架構的支持
5)gcc是自由軟件
gcc編譯過程
預處理(Pre-Processing)
編譯(Compiling)
匯編(Assembling)
鏈接(Linking)
Gcc *.c –o 1exe (總的編譯步驟)
Gcc –E 1.c –o 1.i //宏定義 宏展開
Gcc –S 1.i –o 1.s
Gcc –c 1.s –o 1.o
Gcc 1.o –o 1exe
結論:gcc編譯工具是一個工具鏈。。。。
hello程序是一個高級C語言程序,這種形式容易被人讀懂。為了在系統上運行hello.c程序,每條C語句都必須轉化為低級機器指令。然後將這些指令打包成可執行目標文件格式,並以二進制形式存儲器於磁盤中。
gcc常用編譯選項
選項
作用
-o
產生目標(.i、.s、.o、可執行文件等)
-c
通知gcc取消鏈接步驟,即編譯源碼並在最後生成目標文件
-E
只運行C預編譯器
-S
告訴編譯器產生匯編語言文件後停止編譯,產生的匯編語言文件擴展名為.s
-Wall
使gcc對源文件的代碼有問題的地方發出警告
-Idir
將dir目錄加入搜索頭文件的目錄路徑
-Ldir
將dir目錄加入搜索庫的目錄路徑
-llib
鏈接lib庫
-g
在目標文件中嵌入調試信息,以便gdb之類的調試程序調試
練習
gcc -E hello.c -o hello.i(預處理)
gcc -S hello.i -o hello.s(編譯)
gcc -c hello.s -o hello.o(匯編)
gcc hello.o -o hello(鏈接)
以上四個步驟,可合成一個步驟
gcc hello.c -o hello(直接編譯鏈接成可執行目標文件)
gcc -c hello.c或gcc -c hello.c -o hello.o(編譯生成可重定位目標文件)
建議初學都加這個選項。下面這個例子如果不加-Wall選項編譯器不報任何錯誤,但是得到的結果卻不是預期的。
#include
int main(void)
{
printf("2+1 is %f", 3);
return 0;
}
Gcc編譯多個.c
hello_1.h
hello_1.c
main.c
一次性編譯
gcc hello_1.c main.c –o newhello
獨立編譯
gcc -Wall -c main.c -o main.o
gcc -Wall -c hello_1.c -o hello_fn.o
gcc -Wall main.o hello_1.o -o newhello
命令:g++ -S 7.cpp -o 7.s
.file "7.cpp"
.text
.def __ZL6printfPKcz; .scl 3; .type 32; .endef
__ZL6printfPKcz:
LFB264:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
pushl %ebx
subl $36, %esp
.cfi_offset 3, -12
leal 12(%ebp), %eax
movl %eax, -12(%ebp)
movl -12(%ebp), %eax
movl %eax, 4(%esp)
movl 8(%ebp), %eax
movl %eax, (%esp)
call ___mingw_vprintf
movl %eax, %ebx
movl %ebx, %eax
addl $36, %esp
popl %ebx
.cfi_restore 3
popl %ebp
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE264:
.lcomm __ZStL8__ioinit,1,1
.def ___main; .scl 2; .type 32; .endef
.section .rdata,"dr"
LC0:
.ascii "a:%d b:%d \12\0"
LC1:
.ascii "c1:%c c2:%c \12\0"
LC2:
.ascii "pause\0"
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
LFB1023:
.cfi_startproc
.cfi_personality 0,___gxx_personality_v0
.cfi_lsda 0,LLSDA1023
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $32, %esp
call ___main
movl $0, 28(%esp)
movl $10, 24(%esp)
movb $97, 23(%esp)
movb $98, 22(%esp)
leal 24(%esp), %eax
movl %eax, 4(%esp)
leal 28(%esp), %eax
movl %eax, (%esp)
call __Z6myswapIiEvRT_S1_ //66 ===>126
movl 24(%esp), %edx
movl 28(%esp), %eax
movl %edx, 8(%esp)
movl %eax, 4(%esp)
movl $LC0, (%esp)
call __ZL6printfPKcz
leal 22(%esp), %eax
movl %eax, 4(%esp)
leal 23(%esp), %eax
movl %eax, (%esp)
call __Z6myswapIcEvRT_S1_ //77 ===>155
movzbl 22(%esp), %eax
movsbl %al, %edx
movzbl 23(%esp), %eax
movsbl %al, %eax
movl %edx, 8(%esp)
movl %eax, 4(%esp)
movl $LC1, (%esp)
call __ZL6printfPKcz
movl $LC2, (%esp)
LEHB0:
call _system
LEHE0:
movl $0, %eax
jmp L7
L6:
movl %eax, (%esp)
LEHB1:
call __Unwind_Resume
LEHE1:
L7:
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE1023:
.def ___gxx_personality_v0; .scl 2; .type 32; .endef
.section .gcc_except_table,"w"
LLSDA1023:
.byte 0xff
.byte 0xff
.byte 0x1
.uleb128 LLSDACSE1023-LLSDACSB1023
LLSDACSB1023:
.uleb128 LEHB0-LFB1023
.uleb128 LEHE0-LEHB0
.uleb128 L6-LFB1023
.uleb128 0
.uleb128 LEHB1-LFB1023
.uleb128 LEHE1-LEHB1
.uleb128 0
.uleb128 0
LLSDACSE1023:
.text
.section .text$_Z6myswapIiEvRT_S1_,"x"
.linkonce discard
.globl __Z6myswapIiEvRT_S1_
.def __Z6myswapIiEvRT_S1_; .scl 2; .type 32; .endef
__Z6myswapIiEvRT_S1_: //126
LFB1024:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $16, %esp
movl 8(%ebp), %eax
movl (%eax), %eax
movl %eax, -4(%ebp)
movl 12(%ebp), %eax
movl (%eax), %edx
movl 8(%ebp), %eax
movl %edx, (%eax)
movl 12(%ebp), %eax
movl -4(%ebp), %edx
movl %edx, (%eax)
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE1024:
.section .text$_Z6myswapIcEvRT_S1_,"x"
.linkonce discard
.globl __Z6myswapIcEvRT_S1_
.def __Z6myswapIcEvRT_S1_; .scl 2; .type 32; .endef
__Z6myswapIcEvRT_S1_: //155
LFB1025:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $16, %esp
movl 8(%ebp), %eax
movzbl (%eax), %eax
movb %al, -1(%ebp)
movl 12(%ebp), %eax
movzbl (%eax), %edx
movl 8(%ebp), %eax
movb %dl, (%eax)
movl 12(%ebp), %eax
movzbl -1(%ebp), %edx
movb %dl, (%eax)
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE1025:
.text
.def ___tcf_0; .scl 3; .type 32; .endef
___tcf_0:
LFB1027:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $8, %esp
movl $__ZStL8__ioinit, %ecx
call __ZNSt8ios_base4InitD1Ev
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE1027:
.def __Z41__static_initialization_and_destruction_0ii; .scl 3; .type 32; .endef
__Z41__static_initialization_and_destruction_0ii:
LFB1026:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $24, %esp
cmpl $1, 8(%ebp)
jne L11
cmpl $65535, 12(%ebp)
jne L11
movl $__ZStL8__ioinit, %ecx
call __ZNSt8ios_base4InitC1Ev
movl $___tcf_0, (%esp)
call _atexit
L11:
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE1026:
.def __GLOBAL__sub_I_main; .scl 3; .type 32; .endef
__GLOBAL__sub_I_main:
LFB1028:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $24, %esp
movl $65535, 4(%esp)
movl $1, (%esp)
call __Z41__static_initialization_and_destruction_0ii
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE1028:
.section .ctors,"w"
.align 4
.long __GLOBAL__sub_I_main
.ident "GCC: (rev2, Built by MinGW-builds project) 4.8.0"
.def ___mingw_vprintf; .scl 2; .type 32; .endef
.def _system; .scl 2; .type 32; .endef
.def __Unwind_Resume; .scl 2; .type 32; .endef
.def __ZNSt8ios_base4InitD1Ev; .scl 2; .type 32; .endef
.def __ZNSt8ios_base4InitC1Ev; .scl 2; .type 32; .endef
.def _atexit; .scl 2; .type 32; .endef
編譯器並不是把函數模板處理成能夠處理任意類的函數
編譯器從函數模板通過具體類型產生不同的函數
編譯器會對函數模板進行兩次編譯
在聲明的地方對模板代碼本身進行編譯;在調用的地方對參數替換後的代碼進行編譯。
類模板與函數模板的定義和使用類似,我們已經進行了介紹。 有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同,如下面語句聲明了一個類:
? 類模板用於實現類所需數據的類型參數化
? 類模板在表示如數組、表、圖等數據結構顯得特別重要,
這些數據結構的表示和算法不受所包含的元素類型的影響
//類的類型參數化 抽象的類
//單個類模板
template
class A
{
public:
A(T t)
{
this->t = t;
}
T &getT()
{
return t;
}
protected:
public:
T t;
};
void main()
{
//模板了中如果使用了構造函數,則遵守以前的類的構造函數的調用規則
A
a.getT();
printAA(a);
return ;
}
//結論: 子類從模板類繼承的時候,需要讓編譯器知道 父類的數據類型具體是什麼(數據類型的本質:固定大小內存塊的別名)A
//
class B : public A
{
public:
B(int i) : A
{
}
void printB()
{
cout<<"A:"<
}
protected:
private:
};
//模板與上繼承
//怎麼樣從基類繼承
//若基類只有一個帶參數的構造函數,子類是如何啟動父類的構造函數
void pintBB(B &b)
{
b.printB();
}
void printAA(A
{
//
a.getT();
}
void main()
{
A
a.getT();
printAA(a);
B b(10);
b.printB();
cout<<"hello..."<
system("pause");
return ;
}
//構造函數 沒有問題
//普通函數 沒有問題
//友元函數:用友元函數重載<< >>
// friend ostream& operator<<
//友元函數:友元函數不是實現函數重載(非 << >>)
//1)需要在類前增加 類的前置聲明 函數的前置聲明
template
class Complex;
template
Complex
//2)類的內部聲明 必須寫成:
friend Complex
//3)友元函數實現 必須寫成:
template
Complex
{
Complex
return tmp;
}
//4)友元函數調用必須寫成
Complex
cout<
結論:友元函數只用來進行左移友移操作符重載。
也就是類模板函數說明和類模板實現分開
//類模板函數
構造函數
普通成員函數
友元函數
用友元函數重載<<>>;
用友元函數重載非<<>>
//要包含.cpp
歸納以上的介紹,可以這樣聲明和使用類模板:
1) 先寫出一個實際的類。由於其語義明確,含義清楚,一般不會出錯。
2) 將此類中准備改變的類型名(如int要改變為float或char)改用一個自己指定的虛擬類型名(如上例中的numtype)。
3) 在類聲明前面加入一行,格式為:
template
如:
template
class Compare
{…}; //類體
4) 用類模板定義對象時用以下形式:
類模板名<實際類型名> 對象名;
類模板名<實際類型名> 對象名(實參表列);
如:
Compare
Compare
5) 如果在類模板外定義成員函數,應寫成類模板形式:
template
函數類型 類模板名<虛擬類型參數>::成員函數名(函數形參表列) {…}
關於類模板的幾點說明:
1) 類模板的類型參數可以有一個或多個,每個類型前面都必須加class,如:
template
class someclass
{…};
在定義對象時分別代入實際的類型名,如:
someclass
2) 和使用類一樣,使用類模板時要注意其作用域,只能在其有效作用域內用它定義對象。
3) 模板可以有層次,一個類模板可以作為基類,派生出派生模板類。
? 從類模板實例化的每個模板類有自己的類模板數據成員,該模板類的所有對象共享一個static數據成員
? 和非模板類的static數據成員一樣,模板類的static數據成員也應該在文件范圍定義和初始化
? 每個模板類有自己的類模板的static數據成員副本
原理圖:
小結
? 模板是C++類型參數化的多態工具。C++提供函數模板和類模板。
? 模板定義以模板說明開始。類屬參數必須在模板定義中至少出現一次。
? 同一個類屬參數可以用於多個模板。
? 類屬參數可用於函數的參數類型、返回類型和聲明函數中的變量。
? 模板由編譯器根據實際數據類型實例化,生成可執行代碼。實例化的函數。
模板稱為模板函數;實例化的類模板稱為模板類。
? 函數模板可以用多種方式重載。
? 類模板可以在類層次中使用 。
訓練題
1) 請設計一個數組模板類( MyVector ),完成對int、char、Teacher類型元素的管理。
需求
設計:
類模板 構造函數 拷貝構造函數 <<[] 重載=操作符
a2=a1
實現
2) 請仔細思考:
a)如果數組模板類中的元素是Teacher元素時,需要Teacher類做什麼工作
b)如果數組模板類中的元素是Teacher元素時,Teacher類含有指針屬性哪?
class Teacher
{
friend ostream & operator<<(ostream &out, const Teacher &obj);
public:
Teacher(char *name, int age)
{
this->age = age;
strcpy(this->name, name);
}
Teacher()
{
this->age = 0;
strcpy(this->name, "");
}
private:
int age;
char name[32];
};
class Teacher
{
friend ostream & operator<<(ostream &out, const Teacher &obj);
public:
Teacher(char *name, int age)
{
this->age = age;
strcpy(this->name, name);
}
Teacher()
{
this->age = 0;
strcpy(this->name, "");
}
private:
int age;
char *pname;
};
結論1: 如果把Teacher放入到MyVector數組中,並且Teacher類的屬性含有指針,就是出現深拷貝和淺拷貝的問題。
結論2:需要Teacher封裝的函數有:
1) 重寫拷貝構造函數
2) 重載等號操作符
3) 重載左移操作符。
理論提高:所有容器提供的都是值(value)語意,而非引用(reference)語意。容器執行插入元素的操作時,內部實施拷貝動作。所以STL容器內存儲的元素必須能夠被拷貝(必須提供拷貝構造函數)。
3) 請從數組模板中進行派生
//演示從模板類 派生 一般類
#include "MyVector.cpp"
class MyArray01 : public MyVector
{
public:
MyArray01(int len) : MyVector
{
;
}
protected:
private:
};
//演示從模板類 派生 模板類 //BoundArray
template
class MyArray02 : public MyVector
{
public:
MyArray02(int len) : MyVector
{
;
}
protected:
private:
};
測試案例:
//演示 從模板類 繼承 模板類
void main()
{
MyArray02
dArray2[1] = 3.15;
}
//演示 從模板類 繼承 一般類
void main11()
{
MyArray01 d_array(10);
for (int i=0; i
{
d_array[i] = 3.15;
}
for (int i=0; i
{
cout << d_array[i] << " ";
}
cout<<"hello..."<
system("pause");
return ;
}
封裝你自己的數組類;設計被存儲的元素為類對象;
思考:類對象的類,應該實現的功能。
//1 優化Teacher類, 屬性變成 char*panme, 構造函數裡面分配內存
//2 優化Teacher類,析構函數 釋放panme指向的內存空間
//3 優化Teacher類,避免淺拷貝 重載= 重寫拷貝構造函數
//4 優化Teacher類,在Teacher增加 <<
//5 在模板數組類中,存int charTeacher Teacher*(指針類型)
//=====>stl 容器的概念
C風格的強制類型轉換(TypeCast)很簡單,不管什麼類型的轉換統統是:
TYPE b =(TYPE)a
C++風格的類型轉換提供了4種類型轉換操作符來應對不同場合的應用。
static_cast 靜態類型轉換。如int轉換成char
reinterpreter_cast 重新解釋類型
dynamic_cast 命名上理解是動態類型轉換。如子類和父類之間的多態類型轉換。
const_cast, 字面上理解就是去const屬性。
4種類型轉換的格式:
TYPE B = static_cast
1)static_cast<>() 靜態類型轉換,編譯的時c++編譯器會做類型檢查;
基本類型能轉換 但是不能轉換指針類型
2)若不同類型之間,進行強制類型轉換,用reinterpret_cast<>() 進行重新解釋
3)一般性結論:
C語言中能隱式類型轉換的,在c++中可用 static_cast<>()進行類型轉換。因C++編譯器在編譯檢查一般都能通過;
C語言中不能隱式類型轉換的,在c++中可以用 reinterpret_cast<>() 進行強行類型解釋。總結:static_cast<>()和reinterpret_cast<>() 基本上把C語言中的 強制類型轉換給覆蓋
reinterpret_cast<>()很難保證移植性。
4)dynamic_cast<>(),動態類型轉換,安全的基類和子類之間轉換;運行時類型檢查
5)const_cast<>(),去除變量的只讀屬性
void main01()
{
double dPi = 3.1415926;
//1靜態的類型轉換: 在編譯的時 進行基本類型的轉換 能替代c風格的類型轉換 可以進行一部分檢查
int num1 = static_cast
int num2 = (int)dPi; //c語言的 舊式類型轉換
int num3 = dPi; //隱士類型轉換
cout << "num1:" << num1 << " num2:" << num2 << " num3:" << num3 << endl;
char *p1 = "hello wangbaoming " ;
int *p2 = NULL;
p2 = (int *)p1;
//2 基本類型能轉換 但是不能轉換指針類型
//p2 = static_cast
//3 可以使用 reinterpret_cast 進行重新解釋
p2 = reinterpret_cast
cout << "p1 " << p1 << endl;
cout << "p2 " << p2 << endl;
//4 一般性的結論: c語言中 能隱式類型轉換的 在c++中可以用 static_cast<>()進行類型轉換 //C++編譯器在編譯檢查一般都能通過
//c語言中不能隱式類型轉換的,在c++中可以用 reinterpret_cast<>() 進行強行類型 解釋
system("pause");
return ;
}
class Animal
{
public:
virtual void cry() = 0;
};
class Dog : public Animal
{
public:
virtual void cry()
{
cout << "wangwang " << endl;
}
void doSwim()
{
cout << "我要狗爬" << endl;
}
};
class Cat : public Animal
{
public:
virtual void cry()
{
cout << "miaomiao " << endl;
}
void doTree()
{
cout << "我要爬樹" << endl;
}
};
class Book
{
public:
void printP()
{
cout << price << endl;
}
private:
int price;
};
void ObjPlay(Animal *base)
{
base->cry();
Dog *pDog = dynamic_cast
if (pDog != NULL)
{
pDog->cry();
pDog->doSwim();
}
Cat *pCat = dynamic_cast
if (pCat != NULL)
{
pCat->cry();
pCat->doTree();
}
}
void main02()
{
Animal *base = NULL;
//1 可以把子類指針賦給 父類指針 但是反過來是不可以的 需要 如下轉換
//pdog = base;
Dog *pDog = static_cast
//2 把base轉換成其他 非動物相關的 err
//Book *book= static_cast
//3 reinterpret_cast //可以強制類型轉換
Book *book2= reinterpret_cast
//4 dynamic_cast用法
ObjPlay(new Cat());
system("pause");
}
//典型用法 把形參的只讀屬性去掉
void Opbuf(const char *p)
{
cout << p << endl;
char *p2 = const_cast
p2[0] = 'b';
cout << p << endl;
}
void main()
{
const char *p1 = "11111111111";
char *p2 = "22222222";
char *p3 = const_cast
char buf[100] = "aaaaaaaaaaaa";
Opbuf(buf);
//要保證指針所執行的內存空間能修改才行 若不能修改 還是會引起程序異常
//Opbuf("dddddddddddsssssssssssssss");
system("pause");
}
結論1:程序員要清除的知道: 要轉的變量,類型轉換前是什麼類型,類型轉換後是什麼類型。轉換後有什麼後果。
結論2:一般情況下,不建議進行類型轉換;避免進行類型轉換。
前言
1)異常是一種程序控制機制,與函數機制獨立和互補
函數是一種以棧結構展開的上下函數銜接的程序控制系統,異常是另一種控制結構,它依附於棧結構,卻可以同時設置多個異常類型作為網捕條件,從而以類型匹配在棧機制中跳躍回饋.
2)異常設計目的:
棧機制是一種高度節律性控制機制,面向對象編程卻要求對象之間有方向、有目的的控制傳動,從一開始,異常就是沖著改變程序控制結構,以適應面向對象程序更有效地工作這個主題,而不是僅為了進行錯誤處理。
異常設計出來之後,卻發現在錯誤處理方面獲得了最大的好處。
通過函數返回值來處理錯誤。
1)C++的異常處理機制使得異常的引發和異常的處理不必在同一個函數中,這樣底層的函數可以著重解決具體問題,而不必過多的考慮異常的處理。上層調用者可以再適當的位置設計對不同類型異常的處理。
2)異常是專門針對抽象編程中的一系列錯誤處理的,C++中不能借助函數機制,因為棧結構的本質是先進後出,依次訪問,無法進行跳躍,但錯誤處理的特征卻是遇到錯誤信息就想要轉到若干級之上進行重新嘗試,如圖
3)異常超脫於函數機制,決定了其對函數的跨越式回跳。
4)異常跨越函數
1) 若有異常則通過throw操作創建一個異常對象並拋擲。
2) 將可能拋出異常的程序段嵌在try塊之中。控制通過正常的順序執行到達try語句,然後執行try塊內的保護段。
3) 如果在保護段執行期間沒有引起異常,那麼跟在try塊後的catch子句就不執行。程序從try塊後跟隨的最後一個catch子句後面的語句繼續執行下去。
4) catch子句按其在try塊後出現的順序被檢查。匹配的catch子句將捕獲並處理異常(或繼續拋擲異常)。
5) 如果匹配的處理器未找到,則運行函數terminate將被自動調用,其缺省功能是調用abort終止程序。
6)處理不了的異常,可以在catch的最後一個分支,使用throw語法,向上扔。
案例1:被零整除案例
int divide(int x, int y )
{
if (y ==0)
{
throw x;
}
return x/y;
}
void main41()
{
try
{
cout << "8/2 = " << divide(8, 2) << endl;
cout << "10/0 =" << divide(10, 0) << endl;
}
catch (int e)
{
cout << "e" << " is divided by zero!" << endl;
}
catch(...)
{
cout << "未知異常" << endl;
}
cout << "ok" << endl;
system("pause");
return ;
}
案例2:
class A{};
void f(){
if(...) throw A;
}
void g(){
try{
f();
}catch(B){
cout<<“exception B\n”;
}
}
int main(){
g();
}
throw A將穿透函數f,g和main,抵達系統的最後一道防線——激發terminate函數.
該函數調用引起運行終止的abort函數.
最後一道防線的函數可以由程序員設置.從而規定其終止前的行為.
修改系統默認行為:
u 可以通過set_terminate函數修改捕捉不住異常的默認處理器,從而使得發生捉不住異常時,被自定義函數處理:
u voidmyTerminate(){cout<<“HereIsMyTerminate\n”;}
u set_terminate(myTerminate);
u set_terminate函數在頭文件exception中聲明,參數為函數指針void(*)().
案例3:
v 構造函數沒有返回類型,無法通過返回值來報告運行狀態,所以只通過一種非函數機制的途徑,即異常機制,來解決構造函數的出錯問題。
7)異常機制與函數機制互不干涉,但捕捉的方式是基於類型匹配。捕捉相當於函數返回類型的匹配,而不是函數參數的匹配,所以捕捉不用考慮一個拋擲中的多種數據類型匹配問題
比如:
class A{};
class B{};
int main()
{
try
{
int j = 0;
double d = 2.3;
char str[20] = "Hello";
cout<<"Please input a exception number: ";
int a;
cin>>a;
switch(a)
{
case 1:
throw d;
case 2:
throw j;
case 3:
throw str;
case 4:
throw A();
case 5:
throw B();
default:
cout<<"No throws here.\n";
}
}
catch(int)
{
cout<<"int exception.\n";
}
catch(double)
{
cout<<"double exception.\n";
}
catch(char*)
{
cout<<"char* exception.\n";
}
catch(A)
{
cout<<"class A exception.\n";
}
catch(B)
{
cout<<"class B exception.\n";
}
cout<<"That's ok.\n";
system("pause");
}//====================================
catch代碼塊必須出現在try後,並且在try塊後可以出現多個catch代碼塊,以捕捉各種不同類型的拋擲。
異常機制是基於這樣的原理:程序運行實質上是數據實體在做一些操作,因此發生異常現象的地方,一定是某個實體出了差錯,該實體所對應的數據類型便作為拋擲和捕捉的依據。
8)異常捕捉嚴格按照類型匹配
u 異常捕捉的類型匹配之苛刻程度可以和模板的類型匹配媲美,它不允許相容類型的隱式轉換,比如,拋擲char類型用int型就捕捉不到.例如下列代碼不會輸出“int exception.”,從而也不會輸出“That’s ok.” 因為出現異常後提示退出
int main(){
try{
throw‘H’;
}catch(int){
cout<<"int exception.\n";
}
cout<<"That's ok.\n";
}
異常被拋出後,從進入try塊起,到異常被拋擲前,這期間在棧上的構造的所有對象,都會被自動析構。析構的順序與構造的順序相反。這一過程稱為棧的解旋(unwinding)。
class MyException {};
class Test
{
public:
Test(int a=0, int b=0)
{
this->a = a;
this->b = b;
cout << "Test 構造函數執行" << "a:" << a << " b: " << b << endl;
}
void printT()
{
cout << "a:" << a << " b: " << b << endl;
}
~Test()
{
cout << "Test 析構函數執行" << "a:" << a << " b: " << b << endl;
}
private:
int a;
int b;
};
void myFunc() throw (MyException)
{
Test t1;
Test t2;
cout << "定義了兩個棧變量,異常拋出後測試棧變量的如何被析構" << endl;
throw MyException();
}
void main()
{
//異常被拋出後,從進入try塊起,到異常被拋擲前,這期間在棧上的構造的所有對象,
//都會被自動析構。析構的順序與構造的順序相反。
//這一過程稱為棧的解旋(unwinding)
try
{
myFunc();
}
//catch(MyException &e) //這裡不能訪問異常對象
catch(MyException ) //這裡不能訪問異常對象
{
cout << "接收到MyException類型異常" << endl;
}
catch(...)
{
cout << "未知類型異常" << endl;
}
system("pause");
return ;
}
1)為了加強程序的可讀性,可以在函數聲明中列出可能拋出的所有異常類型,例如:
voidfunc() throw (A, B, C , D); //這個函數func()能夠且只能拋出類型A B C D及其子類型的異常。
2)如果在函數聲明中沒有包含異常接口聲明,則次函數可以拋擲任何類型的異常,例如:
voidfunc();
3)一個不拋擲任何類型異常的函數可以聲明為:
voidfunc() throw();
4) 如果一個函數拋出了它的異常接口聲明所不允許拋出的異常,unexpected函數會被調用,該函數默認行為調用terminate函數中止程序。
1)throw的異常是有類型的,可以使,數字、字符串、類對象。
2)throw的異常是有類型的,catch嚴格按照類型進行匹配。
3)注意 異常對象的內存模型。
//文件的二進制copy
int filecopy01(char *filename2, char *filename1 )
{
FILE *fp1= NULL, *fp2 = NULL;
fp1 = fopen(filename1, "rb");
if (fp1 == NULL)
{
return 1;
}
fp2 = fopen(filename2, "wb");
if (fp1 == NULL)
{
return 2;
}
char buf[256];
int readlen, writelen;
while ( (readlen = fread(buf, 1, 256, fp1)) > 0 ) //如果讀到數據,則大於0
{
writelen = fwrite(buf, 1, readlen, fp2);
if (readlen != readlen)
{
return 3;
}
}
fclose(fp1);
fclose(fp2);
return 0;
}
測試程序
void main11()
{
int ret;
ret = filecopy01("c:/1.txt","c:/2.txt");
if (ret !=0 )
{
switch(ret)
{
case 1:
printf("打開源文件時出錯!\n");
break;
case 2:
printf("打開目標文件時出錯!\n");
break;
case 3:
printf("拷貝文件時出錯!\n");
break;
default:
printf("發生未知錯誤!\n");
break;
}
}
}
/文件的二進制copy
void filecopy02(char *filename2, char *filename1 )
{
FILE *fp1= NULL, *fp2 = NULL;
fp1 = fopen(filename1, "rb");
if (fp1 == NULL)
{
//return 1;
throw 1;
}
fp2 = fopen(filename2, "wb");
if (fp1 == NULL)
{
//return 2;
throw 2;
}
char buf[256];
int readlen, writelen;
while ( (readlen = fread(buf, 1, 256, fp1)) > 0 ) //如果讀到數據,則大於0
{
writelen = fwrite(buf, 1, readlen, fp2);
if (readlen != readlen)
{
//return 3;
throw 3;
}
}
fclose(fp1);
fclose(fp2);
return ;
}
//文件的二進制copy
void filecopy03(char *filename2, char *filename1 )
{
FILE *fp1= NULL, *fp2 = NULL;
fp1 = fopen(filename1, "rb");
if (fp1 == NULL)
{
throw "打開源文件時出錯";
}
fp2 = fopen(filename2, "wb");
if (fp1 == NULL)
{
throw "打開目標文件時出錯";
}
char buf[256];
int readlen, writelen;
while ( (readlen = fread(buf, 1, 256, fp1)) > 0 ) //如果讀到數據,則大於0
{
writelen = fwrite(buf, 1, readlen, fp2);
if (readlen != readlen)
{
throw "拷貝文件過程中失敗";
}
}
fclose(fp1);
fclose(fp2);
return ;
}
//throw int類型變量
//throw 字符串類型
//throw 類類型
class BadSrcFile
{
public:
BadSrcFile()
{
cout << "BadSrcFile 構造 do "<
}
~BadSrcFile()
{
cout << "BadSrcFile 析構 do "<
}
BadSrcFile(BadSrcFile & obj)
{
cout << "拷貝構造 do "<
}
void toString()
{
cout << "aaaa" << endl;
}
};
class BadDestFile {};
class BadCpyFile {};;
void filecopy04(char *filename2, char *filename1 )
{
FILE *fp1= NULL, *fp2 = NULL;
fp1 = fopen(filename1, "rb");
if (fp1 == NULL)
{
//throw new BadSrcFile();
throw BadSrcFile();
}
fp2 = fopen(filename2, "wb");
if (fp1 == NULL)
{
throw BadDestFile();
}
char buf[256];
int readlen, writelen;
while ( (readlen = fread(buf, 1, 256, fp1)) > 0 ) //如果讀到數據,則大於0
{
writelen = fwrite(buf, 1, readlen, fp2);
if (readlen != readlen)
{
throw BadCpyFile();
}
}
fclose(fp1);
fclose(fp2);
return ;
}
main測試案例
//結論://C++編譯器通過throw 來產生對象,C++編譯器再執行對應的catch分支,相當於一個函數調用,把實參傳遞給形參。
void main11()
{
try
{
//filecopy02("c:/1.txt","c:/2.txt");
// filecopy03("c:/1.txt","c:/2.txt");
filecopy04("c:/1.txt","c:/2.txt");
}
catch (int e)
{
printf("發生異常:%d \n", e);
}
catch (const char * e)
{
printf("發生異常:%s \n", e);
}
catch ( BadSrcFile *e)
{
e->toString();
printf("發生異常:打開源文件時出錯!\n");
}
catch ( BadSrcFile &e)
{
e.toString();
printf("發生異常:打開源文件時出錯!\n");
}
catch ( BadDestFile e)
{
printf("發生異常:打開目標文件時出錯!\n");
}
catch ( BadCpyFile e)
{
printf("發生異常:copy時出錯!\n");
}
catch(...) //抓漏網之魚
{
printf("發生了未知異常! 抓漏網之魚\n");
}
//class BadSrcFile {};
//class BadDestFile {};
//class BadCpyFile {};;
}
v 異常是類 – 創建自己的異常類
v 異常派生
v 異常中的數據:數據成員
v 按引用傳遞異常
? 在異常中使用虛函數
案例:設計一個數組類 MyArray,重載[]操作,
數組初始化時,對數組的個數進行有效檢查
1) index<0 拋出異常eNegative
2) index = 0 拋出異常eZero
3)index>1000拋出異常eTooBig
4)index<10 拋出異常eTooSmall
5)eSize類是以上類的父類,實現有參數構造、並定義virtual void printErr()輸出錯誤。
案例1:
// out_of_range
#include "iostream"
using namespace std;
#include
class Teacher
{
public:
Teacher(int age) //構造函數, 通過異常機制 處理錯誤
{
if (age > 100)
{
throw out_of_range("年齡太大");
}
this->age = age;
}
protected:
private:
int age;
};
void mainxx()
{
try
{
Teacher t1(102);
}
catch (out_of_range e)
{
cout << e.what() << endl;
}
exception e;
system("pause");
}
案例2
class Dog
{
public:
Dog()
{
parr = new int[1024*1024*100]; //4MB
}
private:
int *parr;
};
int main31()
{
Dog *pDog;
try{
for(int i=1; i<1024; i++) //40GB!
{
pDog = new Dog();
cout << i << ": new Dog 成功." << endl;
}
}
catch(bad_alloc err)
{
cout << "new Dog 失敗: " << err.what() << endl;
}
return 0;
}
案例3
程序的輸入指的是從輸入文件將數據傳送給程序,程序的輸出指的是從程序將數據傳送給輸出文件。
C++輸入輸出包含以下三個方面的內容:
對系統指定的標准設備的輸入和輸出。即從鍵盤輸入數據,輸出到顯示器屏幕。這種輸入輸出稱為標准的輸入輸出,簡稱標准I/O。
以外存磁盤文件為對象進行輸入和輸出,即從磁盤文件輸入數據,數據輸出到磁盤文件。以外存文件為對象的輸入輸出稱為文件的輸入輸出,簡稱文件I/O。
對內存中指定的空間進行輸入和輸出。通常指定一個字符數組作為存儲空間(實際上可以利用該空間存儲任何信息)。這種輸入和輸出稱為字符串輸入輸出,簡稱串I/O。
C++的I/O對C的發展--類型安全和可擴展性
在C語言中,用printf和scanf進行輸入輸出,往往不能保證所輸入輸出的數據是可靠的安全的。在C++的輸入輸出中,編譯系統對數據類型進行嚴格的檢查,凡是類型不正確的數據都不可能通過編譯。因此C++的I/O操作是類型安全(type safe)的。C++的I/O操作是可擴展的,不僅可以用來輸入輸出標准類型的數據,也可以用於用戶自定義類型的數據。
C++通過I/O類庫來實現豐富的I/O功能。這樣使C++的輸人輸出明顯地優於C 語言中的printf和scanf,但是也為之付出了代價,C++的I/O系統變得比較復雜,要掌握許多細節。
C++編譯系統提供了用於輸入輸出的iostream類庫。iostream這個單詞是由3個部分組成的,即i-o-stream,意為輸入輸出流。在iostream類庫中包含許多用於輸入輸出的類。常用的見表
ios是抽象基類,由它派生出istream類和ostream類,兩個類名中第1個字母i和o分別代表輸入(input)和輸出(output)。 istream類支持輸入操作,ostream類支持輸出操作,iostream類支持輸入輸出操作。iostream類是從istream類和ostream類通過多重繼承而派生的類。其繼承層次見上圖表示。
C++對文件的輸入輸出需要用ifstrcam和ofstream類,兩個類名中第1個字母i和o分別代表輸入和輸出,第2個字母f代表文件 (file)。ifstream支持對文件的輸入操作,ofstream支持對文件的輸出操作。類ifstream繼承了類istream,類ofstream繼承了類ostream,類fstream繼承了類iostream。見圖
I/O類庫中還有其他一些類,但是對於一般用戶來說,以上這些已能滿足需要了。
與iostream類庫有關的頭文件
iostream類庫中不同的類的聲明被放在不同的頭文件中,用戶在自己的程序中用#include命令包含了有關的頭文件就相當於在本程序中聲明了所需 要用到的類。可以換—種說法:頭文件是程序與類庫的接口,iostream類庫的接口分別由不同的頭文件來實現。常用的有
iostream 包含了對輸入輸出流進行操作所需的基本信息。 fstream 用於用戶管理的文件的I/O操作。 strstream 用於字符串流I/O。 stdiostream 用於混合使用C和C + +的I/O機制時,例如想將C程序轉變為C++程序。 iomanip 在使用格式化I/O時應包含此頭文件。在iostream頭文件中定義的流對象
在 iostream 頭文件中定義的類有 ios,istream,ostream,iostream,istream _withassign, ostream_withassign,iostream_withassign 等。
在iostream頭文件中不僅定義了有關的類,還定義了4種流對象,
對象
含義
對應設備
對應的類
c語言中相應的標准文件
cin
標准輸入流
鍵盤
istream_withassign
stdin
cout
標准輸出流
屏幕
ostream_withassign
stdout
cerr
標准錯誤流
屏幕
ostream_withassign
stderr
clog
標准錯誤流
屏幕
ostream_withassign
stderr
在iostream頭文件中定義以上4個流對象用以下的形式(以cout為例):
ostream cout ( stdout);
在定義cout為ostream流類對象時,把標准輸出設備stdout作為參數,這樣它就與標准輸出設備(顯示器)聯系起來,如果有
cout <<3;
就會在顯示器的屏幕上輸出3。
在iostream頭文件中重載運算符
“<<”和“>>”本來在C++中是被定義為左位移運算符和右位移運算符的,由於在iostream頭文件中對它們進行了重載,使它們能用作標准類型數據的輸入和輸出運算符。所以,在用它們的程序中必須用#include命令把iostream包含到程序中。
#include
1)>>a表示將數據放入a對象中。
2)<
標准I/O對象:cin,cout,cerr,clog
cout流對象
cont是console output的縮寫,意為在控制台(終端顯示器)的輸出。強調幾點。
1) cout不是C++預定義的關鍵字,它是ostream流類的對象,在iostream中定義。 顧名思義,流是流動的數據,cout流是流向顯示器的數據。cout流中的數據是用流插入運算符“<<”順序加入的。如果有
cout<<"I "<<"study C++"<<"very hard. << “wang bao ming ";
按順序將字符串"I ", "study C++ ", "very hard."插人到cout流中,cout就將它們送到顯示器,在顯示器上輸出字符串"I study C++ very hard."。cout流是容納數據的載體,它並不是一個運算符。人們關心的是cout流中的內容,也就是向顯示器輸出什麼。
2)用“ccmt<<”輸出基本類型的數據時,可以不必考慮數據是什麼類型,系統會判斷數據的類型,並根據其類型選擇調用與之匹配的運算符重載函數。這個過程都是自動的,用戶不必干預。如果在C語言中用prinf函數輸出不同類型的數據,必須分別指定相應的輸出格式符,十分麻煩,而且容易出錯。C++的I/O機制對用戶來說,顯然是方便而安全的。
3) cout流在內存中對應開辟了一個緩沖區,用來存放流中的數據,當向cout流插人一個endl時,不論緩沖區是否已滿,都立即輸出流中所有數據,然後插入一個換行符,並刷新流(清空緩沖區)。注意如果插人一個換行符”\n“(如cout< 4) 在iostream中只對"<<"和">>"運算符用於標准類型數據的輸入輸出進行了重載,但未對用戶聲明的類型數據的輸入輸出進行重載。如果用戶聲明了新的類型,並希望用"<<"和">>"運算符對其進行輸入輸出,按照重運算符重載來做。
cerr流對象
cerr流對象是標准錯誤流,cerr流已被指定為與顯示器關聯。cerr的作用是向標准錯誤設備(standard error device)輸出有關出錯信息。cerr與標准輸出流cout的作用和用法差不多。但有一點不同:cout流通常是傳送到顯示器輸出,但也可以被重定向輸出到磁盤文件,而cerr流中的信息只能在顯示器輸出。當調試程序時,往往不希望程序運行時的出錯信息被送到其他文件,而要求在顯示器上及時輸出,這時應該用cerr。cerr流中的信息是用戶根據需要指定的。
clog流對象
clog流對象也是標准錯誤流,它是console log的縮寫。它的作用和cerr相同,都是在終端顯示器上顯示出錯信息。區別:cerr是不經過緩沖區,直接向顯示器上輸出有關信息,而clog中的信息存放在緩沖區中,緩沖區滿後或遇endl時向顯示器輸出。
緩沖區的概念:
標准輸入流對象cin,重點掌握的函數
cin.get()//一次只能讀取一個字符
cin.get(一個參數) //讀一個字符
cin.get(三個參數) //可以讀字符串
cin.getline()
cin.ignore()
cin.peek()
cin.putback()
//1 cin cout能根據類型 獲取數據 / 輸出數據
//2 輸入字符串 你 好 遇見空格,停止接受輸入
void main01()
{
char YourName[50];
int myInt;
long myLong;
double myDouble;
float myFloat;
unsigned int myUnsigned;
cout << "請輸入一個Int: ";
cin >> myInt;
cout << "請輸入一個Long: ";
cin >> myLong;
cout << "請輸入一個Double: ";
cin >> myDouble;
cout << "請輸入你的姓名: ";
cin >> YourName;
cout << "\n\n你輸入的數是:" << endl;
cout << "Int: \t" << myInt << endl;
cout << "Long: \t" << myLong << endl;
cout << "Double: \t" << myDouble << endl;
cout << "姓名: \t" << YourName << endl;
cout<< endl << endl;
system("pause");
return ;
}
//1 輸入英文 ok
//2 ctr+z 會產生一個 EOF(-1)
int main02()
{
char ch;
while( (ch= cin.get())!= EOF)
{
std::cout << "字符: " << ch << std::endl;
}
std::cout << "\n結束.\n";
system("pause");
return 0;
}
//演示:讀一個字符 鏈式編程
void main03()
{
char a, b, c;
cin.get(a);
cin.get(b);
cin.get(c);
cout << a << b << c<< endl;
cout << "開始鏈式編程" << endl;
cout.flush();
cin.get(a).get(b).get(c);
cout << a << b << c<< endl;
system("pause");
return ;
}
//演示cin.getline() 可以接受空格
void main04()
{
char buf1[256];
char buf2[256];
cout << "\n請輸入你的字符串 不超過256" ;
cin.getline(buf1, 256, '\n');
cout << buf1 << endl;
//
cout << "注意: cin.getline() 和 cin >> buf2 的區別, 能不能帶空格 " << endl;
cin >> buf2 ; //流提取操作符 遇見空格 停止提取輸入流
cout << buf2 << endl;
system("pause");
}
//緩沖區實驗
/*
1 輸入 "aa bb cc dd" 字符串入緩沖區
2 通過 cin >> buf1; 提走了 aa
3 不需要輸入 可以再通過cin.getline() 把剩余的緩沖區數據提走
*/
void main05()
{
char buf1[256];
char buf2[256];
cout << "請輸入帶有空格的字符串,測試緩沖區" << endl;
cin >> buf1;
cout << "buf1:" << buf1 << endl;
cout << "請輸入數據..." << endl;
//緩沖區沒有數據,就等待; 緩沖區如果有數據直接從緩沖區中拿走數據
cin.getline(buf2, 256);
cout << "buf2:" << buf2 << endl;
system("pause");
}
// ignore 和 peek
void main06()
{
int intchar;
char buf1[256];
char buf2[256];
cout << "請輸入帶有空格的字符串,測試緩沖區 aa bb cc dd ee " << endl;
cin >> buf1;
cout << "buf1:" << buf1 << endl;
cout << "請輸入數據..." << endl;
cin.ignore(2);
//intchar = cin.peek();
//cout << "緩沖區若有數據,返回第一個數據的asc碼:" << intchar << endl;
//緩沖區沒有數據,就等待; 緩沖區如果有數據直接從緩沖區中拿走數據
cin.getline(buf2, 256);
cout << "buf2:" << buf2 << endl;
intchar = cin.peek(); //沒有緩沖區 默認是阻塞模式
cout << "緩沖區若有數據,返回第一個數據的asc碼:" << intchar << endl;
system("pause");
}
//案例:輸入的整數和字符串分開處理
int main07()
{
cout << "Please, enter a number or a word: ";
char c = std::cin.get();
if ( (c >= '0') && (c <= '9') ) //輸入的整數和字符串 分開處理
{
int n; //整數不可能 中間有空格 使用cin >>n
cin.putback (c);
cin >> n;
cout << "You entered a number: " << n << '\n';
}
else
{
string str;
cin.putback (c);
getline (cin,str); // //字符串 中間可能有空格 使用 cin.getline();
cout << "You entered a word: " << str << '\n';
} system("pause");
return 0;
}
/*
標准輸出流對象cout
cout.flush()
cout.put()
cout.write()
cout.width()
cout.fill()
cout.setf(標記)
*/
/*
manipulator(操作符、控制符)
flush
endl
oct
dec
hex
setbase
setw
setfill
setprecision
…
*/
#include "iostream"
using namespace std;
#include
void main81()
{
cout << "hello world" << endl;
cout.put('h').put('e').put('l').put('\n');
cout.write("hello world", 4); //輸出的長度
char buf[] = "hello world";
printf("\n");
cout.write(buf, strlen(buf));
printf("\n");
cout.write(buf, strlen(buf) - 6);
printf("\n");
cout.write(buf, strlen(buf) + 6); //給的大於buf長度 不會幫我們檢查 提高速度
printf("\n");
system("pause");
return ;
}
//使用cout.setf()控制符
void main82()
{
//使用類成員函數
cout << "
cout.width(30);
cout.fill('*');
cout.setf(ios::showbase); //#include
cout.setf(ios::internal); //設置
cout << hex << 123 << "
cout << endl;
cout << endl;
//manipulator(操作符、控制符)
//使用控制閥
cout << "
<< setw(30)
<< setfill('*')
<< setiosflags(ios::showbase) //基數
<< setiosflags(ios::internal)
<< hex
<< 123
<< "
<< endl;
system("pause");
}
在輸出數據時,為簡便起見,往往不指定輸出的格式,由系統根據數據的類型采取默認的格式,但有時希望數據按指定的格式輸出,如要求以十六進制或八進制形式輸出一個 整數,對輸出的小數只保留兩位小數等。有兩種方法可以達到此目的。
1)使用控制符的方法;
2)使用流對象的有關成員函數。分別敘述如下。
使用控制符的方法
int main()
{
int a;
cout<<"input a:";
cin>>a;
cout<<"dec:"< cout<<"hex:"< cout<<"oct:"< char *pt="China"; //pt指向字符串"China" cout< cout< double pi=22.0/7.0; //計算pi值 //按指數形式輸出,8位小數 cout< cout<<"pi="< cout<<"pi="< cout<<"pi="< system("pause"); return 0; } 運行結果如下: 人們在輸入輸出時有一些特殊的要求,如在輸出實數時規定字段寬度,只保留兩位小數,數據向左或向右對齊等。C++提供了在輸入輸出流中使用的控制符(有的書中稱為操縱符) 舉例,輸出雙精度數: 例如:各行小數點對齊。 int main( ) { doublea=123.456,b=3.14159,c=-3214.67; cout< cout< cout< cout< system("pause"); return0; } 輸出如下: // 用流對象的成員函數控制輸出格式 除了可以用控制符來控制輸出格式外,還可以通過調用流對象cout中用於控制輸出格式的成員函數來控制輸出格式。用於控制輸出格式的常用的成員函數如下: 流成員函數setf和控制符setiosflags括號中的參數表示格式狀態,它是通過格式標志來指定的。格式標志在類ios中被定義為枚舉值。因此在引用這些格式標志時要在前面加上類名ios和域運算符“::”。格式標志見表13.5。 例:用流控制成員函數輸出數據。 int main( ) { int a=21; cout.setf(ios::showbase);//顯示基數符號(0x或) cout<<"dec:"< cout.unsetf(ios::dec); //終止十進制的格式設置 cout.setf(ios::hex); //設置以十六進制輸出的狀態 cout<<"hex:"< cout.unsetf(ios::hex); //終止十六進制的格式設置 cout.setf(ios::oct); //設置以八進制輸出的狀態 cout<<"oct:"< cout.unsetf(ios::oct); char *pt="China"; //pt指向字符串"China" cout.width(10); //指定域寬為 cout< cout.width(10); //指定域寬為 cout.fill('*'); //指定空白處以'*'填充 cout< double pi=22.0/7.0; //輸出pi值 cout.setf(ios::scientific); //指定用科學記數法輸出 cout<<"pi="; //輸出"pi=" cout.width(14); //指定域寬為 cout< cout.unsetf(ios::scientific); //終止科學記數法狀態 cout.setf(ios::fixed); //指定用定點形式輸出 cout.width(12); //指定域寬為 cout.setf(ios::showpos); //正數輸出“+”號 cout.setf(ios::internal); //數符出現在左側 cout.precision(6); //保留位小數 cout< system("pause"); return 0; } 運行情況如下: 對程序的幾點說明: 1) 成員函數width(n)和控制符setw(n)只對其後的第一個輸出項有效。如: cout. width(6); 在輸出第一個輸出項20時,域寬為6,因此在20前面有4個空格,在輸出3.14時,width (6)已不起作用,此時按系統默認的域寬輸出(按數據實際長度輸出)。如果要求在輸出數據時都按指定的同一域寬n輸出,不能只調用一次width(n),而必須在輸出每一項前都調用一次width(n>,上面的程序中就是這樣做的。 2) 在表13.5中的輸出格式狀態分為5組,每一組中同時只能選用一種(例如dec、hex和oct中只能選一,它們是互相排斥的)。在用成員函數setf和控制符setiosflags設置輸出格式狀態後,如果想改設置為同組的另一狀態,應當調用成員函數unsetf(對應於成員函數self)或 resetiosflags(對應於控制符setiosflags),先終止原來設置的狀態。然後再設置其他狀態,大家可以從本程序中看到這點。程序在開始雖然沒有用成員函數self和控制符setiosflags設置用dec輸出格式狀態,但系統默認指定為dec,因此要改變為hex或oct,也應當先用unsetf 函數終止原來設置。如果刪去程序中的第7行和第10行,雖然在第8行和第11行中用成員函數setf設置了hex和oct格式,由於未終止dec格式,因此hex和oct的設置均不起作用,系統依然以十進制形式輸出。 同理,程序倒數第8行的unsetf 函數的調用也是不可缺少的。 3) 用setf 函數設置格式狀態時,可以包含兩個或多個格式標志,由於這些格式標志在ios類中被定義為枚舉值,每一個格式標志以一個二進位代表,因此可以用位或運算符“|”組合多個格式標志。如倒數第5、第6行可以用下面一行代替: cout.setf(ios::internal I ios::showpos); //包含兩個狀態標志,用"|"組合 3)可以看到:對輸出格式的控制,既可以用控制符(如例13.2),也可以用cout流的有關成員函數(如例13.3),二者的作用是相同的。控制符是在頭文件iomanip中定義的,因此用控制符時,必須包含iomanip頭文件。cout流的成員函數是在頭文件iostream 中定義的,因此只需包含頭文件iostream,不必包含iomanip。許多程序人員感到使用控制符方便簡單,可以在一個cout輸出語句中連續使用多種控制符。 v 文件輸入流 ifstream v 文件輸出流 ofstream v 文件輸入輸出流 fstream v 文件的打開方式 v 文件流的狀態 v 文件流的定位:文件指針(輸入指針、輸出指針) v 文本文件和二進制文件 輸入輸出是以系統指定的標准設備(輸入設備為鍵盤,輸出設備為顯示器)為對象的。在實際應用中,常以磁盤文件作為對象。即從磁盤文件讀取數據,將數據輸出到磁盤文件。 和文件有關系的輸入輸出類主要在fstream.h這個頭文件中被定義,在這個頭文件中主要被定義了三個類,由這三個類控制對文件的各種輸入輸出操作,他們分別是ifstream、ofstream、fstream,其中fstream類是由iostream類派生而來,他們之間的繼承關系見下圖所示。 由於文件設備並不像顯示器屏幕與鍵盤那樣是標准默認設備,所以它在fstream.h頭文件中是沒有像cout那樣預先定義的全局對象,所以我們必須自己定義一個該類的對象。 ifstream類,它是從istream類派生的,用來支持從磁盤文件的輸入。 ofstream類,它是從ostream類派生的,用來支持向磁盤文件的輸出。 fstream類,它是從iostream類派生的,用來支持對磁盤文件的輸入輸出。 所謂打開(open)文件是一種形象的說法,如同打開房門就可以進入房間活動一樣。打開文件是指在文件讀寫之前做必要的准備工作,包括: 1)為文件流對象和指定的磁盤文件建立關聯,以便使文件流流向指定的磁盤文件。 2)指定文件的工作方式,如,該文件是作為輸入文件還是輸出文件,是ASCII文件還是二進制文件等。 以上工作可以通過兩種不同的方法實現。 1) 調用文件流的成員函數open。如 ofstream outfile; //定義ofstream類(輸出文件流類)對象outfile outfile.open("f1.dat",ios::out); //使文件流與f1.dat文件建立關聯 第2行是調用輸出文件流的成員函數open打開磁盤文件f1.dat,並指定它為輸出文件,文件流對象outfile將向磁盤文件f1.dat輸出數據。ios::out是I/O模式的一種,表示以輸出方式打開一個文件。或者簡單地說,此時f1.dat是一個輸出文件,接收從內存輸出的數據。 調用成員函數open的一般形式為: 文件流對象.open(磁盤文件名, 輸入輸出方式); 磁盤文件名可以包括路徑,如"c:\new\\f1.dat",如缺省路徑,則默認為當前目錄下的文件。 2) 在定義文件流對象時指定參數 在聲明文件流類時定義了帶參數的構造函數,其中包含了打開磁盤文件的功能。因此,可以在定義文件流對象時指定參數,調用文件流類的構造函數來實現打開文件的功能。如 ostream outfile("f1.dat",ios::out); 一般多用此形式,比較方便。作用與open函數相同。 輸入輸出方式是在ios類中定義的,它們是枚舉常量,有多種選擇,見表13.6。 幾點說明: 在對已打開的磁盤文件的讀寫操作完成後,應關閉該文件。關閉文件用成員函數close。如 如果文件的每一個字節中均以ASCII代碼形式存放數據,即一個字節存放一個字符,這個文件就是ASCII文件(或稱字符文件)。程序可以從ASCII文件中讀入若干個字符,也可以向它輸出一些字符。 1)用流插入運算符“<<”和流提取運算符“>>”輸入輸出標准類型的數據。“<<”和“ >>”都巳在iostream中被重載為能用於ostream和istream類對象的標准類型的輸入輸出。由於ifstream和 ofstream分別是ostream和istream類的派生類;因此它們從ostream和istream類繼承了公用的重載函數,所以在對磁盤文件的操作中,可以通過文件流對象和流插入運算符“<<”及流提取運算符“>>”實現對磁盤文件的讀寫,如同用cin、cout和<<、>>對標准設備進行讀寫一樣。 2)用文件流的put、get、geiline等成員函數進行字符的輸入輸出,:用C++流成員函數put輸出單個字符、C++ get()函數讀入一個字符和C++ getline()函數讀入一行字符。 #include using namespace std; #include "fstream" int main92() { char fileName[80]; char buffer[255]; cout << "請輸入一個文件名: "; cin >> fileName; ofstream fout(fileName, ios::app); fout << "1111111111111111111\n"; fout << "22222222222222222\n"; //cin.ignore(1,'\n'); cin.getline(buffer,255); //從鍵盤輸入 fout << buffer << "\n"; fout.close(); ifstream fin(fileName); cout << "Here's the the content of the file: \n"; char ch; while(fin.get(ch)) cout << ch; cout << "\n***End of file contents.***\n"; fin.close(); system("pause"); return 0; } ofstream類的默認構造函數原形為: ofstream::ofstream(constchar *filename, intmode = ios::out, int penprot = filebuf::openprot); ·filename: 要打開的文件名 ·mode: 要打開文件的方式 ·prot: 打開文件的屬性 其中mode和openprot這兩個參數的可選項表見下表: mode屬性表 ios::app 以追加的方式打開文件 ios::ate 文件打開後定位到文件尾,ios:app就包含有此屬性 ios::binary 以二進制方式打開文件,缺省的方式是文本方式。兩種方式的區別見前文 ios::in 文件以輸入方式打開 ios::out 文件以輸出方式打開 ios::trunc 如果文件存在,把文件長度設為0 可以用“|”把以上屬性連接起來,如ios::out|ios::binary。 openprot屬性表 屬性 含義 0 普通文件,打開訪問 1 只讀文件 2 隱含文件 4 系統文件 可以用“或”或者“+”把以上屬性連接起來 ,如3或1|2就是以只讀和隱含屬性打開文件。 #include 文件使用完後可以使用close成員函數關閉文件。 ios::app為追加模式,在使用追加模式的時候同時進行文件狀態的判斷是一個比較好的習慣。 #include 在定義ifstream和ofstream類對象的時候,我們也可以不指定文件。以後可以通過成員函數open()顯式的把一個文件連接到一個類對象上。 例如: #include 下面我們來看一下是如何利用ifstream類對象,將文件中的數據讀取出來,然後再輸出到標准設備中的例子。 #include 上例中,我們利用成員函數get(),逐一的讀取文件中的有效字符,再利用put()成員函數,將文件中的數據通過循環逐一輸出到標准設備(屏幕) 上, get()成員函數會在文件讀到默尾的時候返回假值,所以我們可以利用它的這個特性作為while循環的終止條件,我們同時也在上例中引入了C++風格的字符串類型string,在循環讀取的時候逐一保存到content中,要使用string類型,必須包含string.h的頭文件。 我們在簡單介紹過ofstream類和ifstream類後,我們再來看一下fstream類,fstream類是由iostream派生而來,fstream類對象可以同對文件進行讀寫操作。 #include 由於fstream類可以對文件同時進行讀寫操作,所以對它的對象進行初始話的時候一定要顯式的指定mode和openprot參數。 二進制文件不是以ASCII代碼存放數據的,它將內存中數據存儲形式不加轉換地傳送到磁盤文件,因此它又稱為內存數據的映像文件。因為文件中的信息不是字符數據,而是字節中的二進制形式的信息,因此它又稱為字節文件。 對二進制文件的操作也需要先打開文件,用完後要關閉文件。在打開時要用ios::binary指定為以二進制形式傳送和存儲。二進制文件除了可以作為輸入文件或輸出文件外,還可以是既能輸入又能輸出的文件。這是和ASCII文件不同的地方。 對二進制文件的讀寫主要用istream類的成員函數read和write來實現。這兩個成員函數的原型為 //二進制 int main() { charfileName[255] = "c:/teacher.dat"; ofstreamfout(fileName,ios::binary); if(!fout) { cout<< "Unable to open " << fileName << " forwriting.\n"; return(1); } Teachert1(31, "31"); Teachert2(32, "32"); fout.write((char*)&t1,sizeof Teacher); fout.write((char*)&t2,sizeof Teacher); fout.close(); cout<< "保存對象到二進制文件裡成功!" << endl; ifstreamfin(fileName,ios::binary); if(!fin) { cout<< "Unable to open " << fileName << " forreading.\n"; return(1); } Teachertmp(100,"100"); fin.read((char*)&tmp,sizeof Teacher); tmp.printT(); fin.read((char*)&tmp,sizeof Teacher); tmp.printT(); system("pause"); return0; } 1 編程實現以下數據輸入/輸出: (1)以左對齊方式輸出整數,域寬為12。 (2)以八進制、十進制、十六進制輸入/輸出整數。 (3)實現浮點數的指數格式和定點格式的輸入/輸出,並指定精度。 (4)把字符串讀入字符型數組變量中,從鍵盤輸入,要求輸入串的空格也全部讀入,以回車符結束。 (5)將以上要求用流成員函數和操作符各做一遍。 2編寫一程序,將兩個文件合並成一個文件。 3編寫一程序,統計一篇英文文章中單詞的個數與行數。 4編寫一程序,將C++源程序每行前加上行號與一個空格。 4.5編寫一程序,輸出ASCII碼值從20到127的ASCII碼字符表,格式為每行10個。 參考答案: 第一題 Ios類成員函數實現 #include #include using namespace std; int main(){ long a=234; double b=2345.67890; char c[100]; cout.fill('*'); cout.flags(ios_base::left); cout.width(12); cout< cout.fill('*'); cout.flags(ios::right); cout.width(12); cout< cout.flags(ios.hex); cout<<234<<'\t'; cout.flags(ios.dec); cout<<234<<'\t'; cout.flags(ios.oct); cout<<234< cout.flags(ios::scientific); cout< cout.flags(ios::fixed); cout< cin.get(c,99); cout< return 0; } 操作符實現 #include #include using namespace std; int main(){ long a=234; double b=2345.67890; char c[100]; cout< cout< cout< cout< cout< return 0; } 第二題: #include #include using namespace std; int main(){ int i=1; char c[1000]; ifstream ifile1("D:\\1.cpp"); ifstream ifile2("D:\\2.cpp"); ofstream ofile("D:\\3.cpp"); while(!ifile1.eof()){ ifile1.getline(c,999); ofile< } while(!ifile2.eof()){ ifile2.getline(c,999); ofile< } ifile1.close(); ifile2.close(); ofile.close(); return 0; } 第三題 #include #include using namespace std; bool isalph(char); int main(){ ifstream ifile("C:\\daily.doc"); char text[1000]; bool inword=false; int rows=0,words=0; int i; while(!ifile.eof()){ ifile.getline(text,999); rows++; i=0; while(text[i]!=0){ if(!isalph(text[i])) inword=false; else if(isalph(text[i]) && inword==false){ words++; inword=true; } i++; } } cout<<"rows= "< cout<<"words= "< ifile.close (); return 0; } bool isalph(char c){ return ((c>='A' && c<='Z') || (c>='a' && c<='z')); } 第四題 #include #include using namespace std; int main(){ int i=1; char c[1000]; ifstream ifile("D:\\1.cpp"); ofstream ofile("D:\\2.cpp"); while(!ifile.eof()){ ofile< ifile.getline(c,999); ofile< } ifile.close(); ofile.close(); return 0; } 第五題 #include using namespace std; int main(){ int i,l; for(i=32;i<127;i++){ cout< l++; if(l%10==0)cout< } cout< return 0; } STL(Standard Template Library,標准模板庫)是惠普實驗室開發的一系列軟件的統稱。現然主要出現在C++中,但在被引入C++之前該技術就已經存在了很長的一段時間。 STL的從廣義上講分為三類:algorithm(算法)、container(容器)和iterator(迭代器),容器和算法通過迭代器可以進行無縫地連接。幾乎所有的代碼都采 用了模板類和模板函數的方式,這相比於傳統的由函數和類組成的庫來說提供了更好的代碼重用機會。在C++標准中,STL被組織為下面的13個頭文 件: STL詳細的說六大組件 – 容器(Container) – 算法(Algorithm) – 迭代器(Iterator) – 仿函數(Function object) – 適配器(Adaptor) – 空間配制器(allocator) 使用STL的好處 1)STL是C++的一部分,因此不用額外安裝什麼,它被內建在你的編譯器之內。 2)STL的一個重要特點是數據結構和算法的分離。盡管這是個簡單的概念,但是這種分離確實使得STL變得非常通用。 例如,在STL的vector容器中,可以放入元素、基礎數據類型變量、元素的地址; STL的sort()函數可以用來操作vector,list等容器。 3)程序員可以不用思考STL具體的實現過程,只要能夠熟練使用STL就OK了。這樣他們就可以把精力放在程序開發的別的方面。 4) STL具有高可重用性,高性能,高移植性,跨平台的優點。 高可重用性:STL中幾乎所有的代碼都采用了模板類和模版函數的方式實現,這相比於傳統的由函數和類組成的庫來說提供了更好的代碼重用機會。關於模板的知識,已經給大家介紹了。 高性能:如map可以高效地從十萬條記錄裡面查找出指定的記錄,因為map是采用紅黑樹的變體實現的。(紅黑樹是平橫二叉樹的一種) 高移植性:如在項目A上用STL編寫的模塊,可以直接移植到項目B上。 跨平台:如用windows的VisualStudio編寫的代碼可以在Mac OS的XCode上直接編譯。 5) 程序員可以不用思考STL具體的實現過程,只要能夠熟練使用STL就OK了。這樣他們就可以把精力放在程序開發的別的方面。 6) 了解到STL的這些好處,我們知道STL無疑是最值得C++程序員驕傲的一部分。每一個C++程序員都應該好好學習STL。只有能夠熟練使用STL的程序員,才是好的C++程序員。 7)總之:招聘工作中,經常遇到C++程序員對STL不是非常了解。大多是有一個大致的映像,而對於在什麼情況下應該使用哪個容器和算法都感到比較茫然。STL是C++程序員的一項不可或缺的基本技能,掌握它對提升C++編程大有裨益。 在實際的開發過程中,數據結構本身的重要性不會遜於操作於數據結構的算法的重要性,當程序中存在著對時間要求很高的部分時,數據結構的選擇就顯得更加重要。 經典的數據結構數量有限,但是我們常常重復著一些為了實現向量、鏈表等結構而編寫的代碼,這些代碼都十分相似,只是為了適應不同數據的變化而在細節上有所出入。STL容器就為我們提供了這樣的方便,它允許我們重復利用已有的實現構造自己的特定類型下的數據結構,通過設置一些模板,STL容器對最常用的數據結構提供了支持,這些模板的參數允許我們指定容器中元素的數據類型,可以將我們許多重復而乏味的工作簡化。 容器部分主要由頭文 件 用來管理一組元素 序列式容器(Sequence containers) 每個元素都有固定位置--取決於插入時機和地點,和元素值無關。 vector、deque、list 關聯式容器(Associated containers) 元素位置取決於特定的排序准則,和插入順序無關 set、multiset、map、multimap 數據結構 描述 實現頭文件 向量(vector) 連續存儲的元素 列表(list) 由節點組成的雙向鏈表,每個結點包含著一個元素 雙隊列(deque) 連續存儲的指向不同元素的指針所組成的數組 集合(set) 由節點組成的紅黑樹,每個節點都包含著一個元素,節點之間以某種作用於元素對的謂詞排列,沒有兩個不同的元素能夠擁有相同的次序 多重集合(multiset) 允許存在兩個次序相等的元素的集合 棧(stack) 後進先出的值的排列 隊列(queue) 先進先出的執的排列 優先隊列(priority_queue) 元素的次序是由作用於所存儲的值對上的某種謂詞決定的的一種隊列 映射(map) 由{鍵,值}對組成的集合,以某種作用於鍵對上的謂詞排列 多重映射(multimap) 允許鍵對有相等的次序的映射 迭代器從作用上來說是最基本的部分,可是理解起來比前兩者都要費力一些。軟件設計有一個基本原則,所有的問題都可以通過引進一個間接層來簡化,這種簡化在STL中就是用迭代器來完成的。概括來說,迭代器在STL中用來將算法和容器聯系起來,起著一種黏和劑的作用。幾乎STL提供的所有算法都是通過迭代器存取元素序列進行工作的,每一個容器都定義了其本身所專有的迭代器,用以存取容器中的元素。 迭代器部分主要由頭文件 函數庫對數據類型的選擇對其可重用性起著至關重要的作用。舉例來說,一個求方根的函數,在使用浮點數作為其參數類型的情況下的可重用性肯定比使用整型作為它的參數類性要高。而C++通過模板的機制允許推遲對某些類型的選擇,直到真正想使用模板或者說對模板進行特化的時候,STL就利用了這一點提供了相當多的有用算法。它是在一個有效的框架中完成這些算法的——可以將所有的類型劃分為少數的幾類,然後就可以在模版的參數中使用一種類型替換掉同一種類中的其他類型。 STL提供了大約100個實現算法的模版函數,比如算法for_each將為指定序列中的每一個元素調用指定的函數,stable_sort以你所指定的規則對序列進行穩定性排序等等。這樣一來,只要熟悉了STL之後,許多代碼可以被大大的化簡,只需要通過調用一兩個算法模板,就可以完成所需要的功能並大大地提升效率。 算法部分主要由頭文件 C++強大的功能來源於其豐富的類庫及庫函數資源。C++標准庫的內容總共在50個標准頭文件中定義。在C++開發中,要盡可能地利用標准庫完成。這樣做的直接好處包括:(1)成本:已經作為標准提供,何苦再花費時間、人力重新開發呢;(2)質量:標准庫的都是經過嚴格測試的,正確性有保證;(3)效率:關於人的效率已經體現在成本中了,關於代碼的執行效率要相信實現標准庫的大牛們的水平;(4)良好的編程風格:采用行業中普遍的做法進行開發。 在C++程序設計課程中,尤其是作為第一門程序設計課程,我們注重了語法、語言的機制等方面的內容。程序設計能力的培養有個過程,跨過基本的原理性知識直接進入到工程中的普遍做法,由於跨度決定了其難度。再者,在掌握了基本原理的基礎上,在認識標准庫的問題上完全可以憑借實踐,逐步地掌握。標准庫的學習不需要認認真真地讀書,需要的是在了解概貌的情況下,在實踐中深入。 這個任務就是要知道C++程序設計課程中不講的,但對程序設計又很重要的這部分內容。至少我們要能先回答出“有什麼”的問題。 C++標准庫的內容分為10類,分別是(建議在閱讀中,將你已經用過或聽說過的頭文件劃出來): C1. 標准庫中與語言支持功能相關的頭文件 頭文件 描 述 定義宏NULL和offsetof,以及其他標准類型size_t和ptrdiff_t。與對應的標准C頭文件的區別是,NULL是C++空指針常量的補充定義,宏offsetof接受結構或者聯合類型參數,只要他們沒有成員指針類型的非靜態成員即可。 提供與基本數據類型相關的定義。例如,對於每個數值數據類型,它定義了可以表示出來的最大值和最小值以及二進制數字的位數。 提供與基本整數數據類型相關的C樣式定義。這些信息的C++樣式定義在 提供與基本浮點型數據類型相關的C樣式定義。這些信息的C++樣式定義在 提供支持程序啟動和終止的宏和函數。這個頭文件還聲明了許多其他雜項函數,例如搜索和排序函數,從字符串轉換為數值等函數。它與對應的標准C頭文件 stdlib.h不同,定義了abort(void)。abort()函數還有額外的功能,它不為靜態或自動對象調用析構函數,也不調用傳給 atexit()函數的函數。它還定義了exit()函數的額外功能,可以釋放靜態對象,以注冊的逆序調用用atexit()注冊的函數。清除並關閉所有 打開的C流,把控制權返回給主機環境。 支持動態內存分配 支持變量在運行期間的類型標識 支持異常處理,這是處理程序中可能發生的錯誤的一種方式 支持接受數量可變的參數的函數。即在調用函數時,可以給函數傳送數量不等的數據項。它定義了宏va_arg、va_end、va_start以及va_list類型 為C樣式的非本地跳躍提供函數。這些函數在C++中不常用 為中斷處理提供C樣式支持 C2. 支持流輸入/輸出的頭文件 頭文件 描 述 支持標准流cin、cout、cerr和clog的輸入和輸出,它還支持多字節字符標准流wcin、wcout、wcerr和wclog。 提供操縱程序,允許改變流的狀態,從而改變輸出的格式。 定義iostream的基類 為管理輸出流緩存區的輸入定義模板類 為管理輸出流緩存區的輸出定義模板類 支持字符串的流輸入輸出 支持文件的流輸入輸出 為輸入輸出對象提供向前的聲明 支持流輸入和輸出的緩存 為標准流提供C樣式的輸入和輸出 支持多字節字符的C樣式輸入輸出 C3. 與診斷功能相關的頭文件 頭文件 描 述 定義標准異常。異常是處理錯誤的方式 定義斷言宏,用於檢查運行期間的情形 支持C樣式的錯誤信息 C4. 定義工具函數的頭文件 頭文件 描 述 定義重載的關系運算符,簡化關系運算符的寫入,它還定義了pair類型,該類型是一種模板類型,可以存儲一對值。這些功能在庫的其他地方使用 定義了許多函數對象類型和支持函數對象的功能,函數對象是支持operator()()函數調用運算符的任意對象 給容器、管理內存的函數和auto_ptr模板類定義標准內存分配器 支持系統時鐘函數 C5. 支持字符串處理的頭文件 頭文件 描 述 為字符串類型提供支持和定義,包括單字節字符串(由char組成)的string和多字節字符串(由wchar_t組成) 單字節字符類別 多字節字符類別 為處理非空字節序列和內存塊提供函數。這不同於對應的標准C庫頭文件,幾個C樣式字符串的一般C庫函數被返回值為const和非const的函數對替代了 為處理、執行I/O和轉換多字節字符序列提供函數,這不同於對應的標准C庫頭文件,幾個多字節C樣式字符串操作的一般C庫函數被返回值為const和非const的函數對替代了。 為把單字節字符串轉換為數值、在多字節字符和多字節字符串之間轉換提供函數 C6. 定義容器類的模板的頭文件 定義vector序列模板,這是一個大小可以重新設置的數組類型,比普通數組更安全、更靈活 定義list序列模板,這是一個序列的鏈表,常常在任意位置插入和刪除元素 定義deque序列模板,支持在開始和結尾的高效插入和刪除操作 為隊列(先進先出)數據結構定義序列適配器queue和priority_queue 為堆棧(後進先出)數據結構定義序列適配器stack map是一個關聯容器類型,允許根據鍵值是唯一的,且按照升序存儲。multimap類似於map,但鍵不是唯一的。 set是一個關聯容器類型,用於以升序方式存儲唯一值。multiset類似於set,但是值不必是唯一的。 為固定長度的位序列定義bitset模板,它可以看作固定長度的緊湊型bool數組 C7. 支持迭代器的頭文件 頭文件 描 述 給迭代器提供定義和支持 C8. 有關算法的頭文件 頭文件 描 述 提供一組基於算法的函數,包括置換、排序、合並和搜索 聲明C標准庫函數bsearch()和qsort(),進行搜索和排序 允許在代碼中使用and代替&& C9. 有關數值操作的頭文件 頭文件 描 述 支持復雜數值的定義和操作 支持數值矢量的操作 在數值序列上定義一組一般數學操作,例如accumulate和inner_product 這是C數學庫,其中還附加了重載函數,以支持C++約定 提供的函數可以提取整數的絕對值,對整數進行取余數操作 C10. 有關本地化的頭文件 頭文件 描 述 提供的本地化包括字符類別、排序序列以及貨幣和日期表示。 對本地化提供C樣式支持 C++標准庫的所有頭文件都沒有擴展名。C++標准庫以 2 模板是實現代碼重用機制的一種工具,實質就是實現類型參數化,即把類型定義為參數。 2 C++提供兩種模板:函數模板,類模板 函數模板的簡介 2 函數模板就是建立一個通用的函數,其函數返回類型和形參類型不具體指定,而是用虛擬的類型來代表。 2 凡是函數體相同的函數都可以用函數模板來代替,不必定義多個函數,只需在模板中定義一次即可。 2 在調用函數時系統會根據實參的類型來取代模板中的虛擬類型,從而實現了不同函數的功能。 類模板的簡介 2 我們先來看一下下面這個類,求最大值的類 2 和函數模板一樣,類模板就是建立一個通用類,其數據成員的類型、成員函數的返回類型和參數類形都可以不具體指定,而用虛擬的類型來代表。 2 當使用類模板建立對象時,系統會根據實參的類型取代類模板中的虛擬類型,從而實現不同類的功能。 2 string是STL的字符串類型,通常用來表示字符串。而在使用string之前,字符串通常是用char*表示的。string與char*都可以用來表示字符串,那麼二者有什麼區別呢。 string和char*的比較 2 string是一個類, char*是一個指向字符的指針。 string封裝了char*,管理這個字符串,是一個char*型的容器。 2 string不用考慮內存釋放和越界。 string管理char*所分配的內存。每一次string的復制,取值都由string類負責維護,不用擔心復制越界和取值越界等。 2 string提供了一系列的字符串操作函數(這個等下會詳講) 查找find,拷貝copy,刪除erase,替換replace,插入insert 2 默認構造函數: string(); //構造一個空的字符串string s1。 2 拷貝構造函數: string(const string &str); //構造一個與str一樣的string。如strings1(s2)。 2 帶參數的構造函數 string(const char *s); //用字符串s初始化 string(int n,char c); //用n個字符c初始化 2 string類的字符操作: const char &operator[] (int n) const; const char &at(int n) const; char &operator[] (int n); char &at(int n); 2 operator[]和at()均返回當前字符串中第n個字符,但二者是有區別的。 主要區別在於at()在越界時會拋出異常,[]在剛好越界時會返回(char)0,再繼續越界時,編譯器直接出錯。如果你的程序希望可以通過try,catch捕獲異常,建議采用at()。 2 const char *c_str() const;//返回一個以'\0'結尾的字符串的首地址 2 int copy(char *s, int n, int pos=0) const; 把當前串中以pos開始的n個字符拷貝到以s為起始位置的字符數組中,返回實際拷貝的數目。注意要保證s所指向的空間足夠大以容納當前字符串,不然會越界。 int length() const; //返回當前字符串的長度。長度不包括字符串結尾的'\0'。 bool empty() const; //當前字符串是否為空 string &operator=(const string&s);//把字符串s賦給當前的字符串 string &assign(const char *s); //把字符串s賦給當前的字符串 string &assign(const char *s, int n);//把字符串s的前n個字符賦給當前的字符串 string &assign(const string&s); //把字符串s賦給當前字符串 string &assign(int n,char c); //用n個字符c賦給當前字符串 string &assign(const string &s,intstart, int n); //把字符串s中從start開始的n個字符賦給當前字符串 string &operator+=(const string&s); //把字符串s連接到當前字符串結尾 string &operator+=(const char *s);//把字符串s連接到當前字符串結尾 string &append(const char *s); //把字符串s連接到當前字符串結尾 string &append(const char *s,intn); //把字符串s的前n個字符連接到當前字符串結尾 string &append(const string&s); //同operator+=() string &append(const string &s,intpos, int n);//把字符串s中從pos開始的n個字符連接到當前字符串結尾 string &append(int n, char c); //在當前字符串結尾添加n個字符c int compare(const string &s)const; //與字符串s比較 int compare(const char *s) const; //與字符串s比較 compare函數在>時返回 1,<時返回 -1,==時返回 0。比較區分大小寫,比較時參考字典順序,排越前面的越小。大寫的A比小寫的a小。 string substr(int pos=0, int n=npos)const; //返回由pos開始的n個字符組成的子字符串 查找 int find(char c,int pos=0) const; //從pos開始查找字符c在當前字符串的位置 int find(const char *s, int pos=0)const; //從pos開始查找字符串s在當前字符串的位置 int find(const string &s, int pos=0)const; //從pos開始查找字符串s在當前字符串中的位置 find函數如果查找不到,就返回-1 int rfind(char c, int pos=npos) const; //從pos開始從後向前查找字符c在當前字符串中的位置 int rfind(const char *s, int pos=npos)const; int rfind(const string &s, intpos=npos) const; //rfind是反向查找的意思,如果查找不到, 返回-1 替換 string &replace(int pos, int n, constchar *s);//刪除從pos開始的n個字符,然後在pos處插入串s string &replace(int pos, int n, conststring &s); //刪除從pos開始的n個字符,然後在pos處插入串s void swap(string &s2); //交換當前字符串與s2的值 //4 字符串的查找和替換 void main25() { strings1 = "wbm hello wbm 111 wbm 222 wbm 333"; size_tindex = s1.find("wbm", 0); cout<< "index: " << index; //求itcast出現的次數 size_toffindex = s1.find("wbm", 0); while(offindex != string::npos) { cout<< "在下標index: " << offindex << "找到wbm\n"; offindex= offindex + 1; offindex= s1.find("wbm", offindex); } //替換 strings2 = "wbm hello wbm 111 wbm 222 wbm 333"; s2.replace(0,3, "wbm"); cout<< s2 << endl; //求itcast出現的次數 offindex= s2.find("wbm", 0); while(offindex != string::npos) { cout<< "在下標index: " << offindex << "找到wbm\n"; s2.replace(offindex,3, "WBM"); offindex= offindex + 1; offindex= s1.find("wbm", offindex); } cout<< "替換以後的s2:" << s2 << endl; } string &insert(int pos, const char *s); string &insert(int pos, const string&s); //前兩個函數在pos位置插入字符串s string &insert(int pos, int n, charc); //在pos位置 插入n個字符c string &erase(int pos=0, intn=npos); //刪除pos開始的n個字符,返回修改後的字符串 void main27() { strings2 = "AAAbbb"; transform(s2.begin(),s2.end(), s2.begin(), toupper); cout<< s2 << endl; strings3 = "AAAbbb"; transform(s3.begin(),s3.end(), s3.begin(), tolower); cout<< s3 << endl; } 2 vector是將元素置於一個動態數組中加以管理的容器。 2 vector可以隨機存取元素(支持索引值直接存取,用[]操作符或at()方法,這個等下會詳講)。 vector尾部添加或移除元素非常快速。但是在中部或頭部插入元素或移除元素比較費時 vector采用模板類實現,vector對象的默認構造形式 vector vector vector vector ... //尖括號內還可以設置指針類型或自定義類型。 Class CA{}; vector vector 理論知識 2 vector(beg,end); //構造函數將[beg,end)區間中的元素拷貝給本身。注意該區間是左閉右開的區間。 2 vector(n,elem); //構造函數將n個elem拷貝給本身。 2 vector(const vector &vec);//拷貝構造函數 intiArray[] = {0,1,2,3,4}; vector vector vector vector vector 理論知識 2 vector.assign(beg,end); //將[beg, end)區間中的數據拷貝賦值給本身。注意該區間是左閉右開的區間。 2 vector.assign(n,elem); //將n個elem拷貝賦值給本身。 2 vector& operator=(const vector&vec); //重載等號操作符 2 vector.swap(vec); // 將vec與本身的元素互換。 vector intiArray[] = {0,1,2,3,4}; vecIntA.assign(iArray,iArray+5); vecIntB.assign( vecIntA.begin(), vecIntA.end() ); //用其它容器的迭代器作參數。 vecIntC.assign(3,9); vector vecIntD = vecIntA; vecIntA.swap(vecIntD); 理論知識 2 vector.size(); //返回容器中元素的個數 2 vector.empty(); //判斷容器是否為空 2 vector.resize(num); //重新指定容器的長度為num,若容器變長,則以默認值填充新位置。如果容器變短,則末尾超出容器長度的元素被刪除。 2 vector.resize(num, elem); //重新指定容器的長度為num,若容器變長,則以elem值填充新位置。如果容器變短,則末尾超出容器長度的元素被刪除。 例如 vecInt是vector int iSize = vecInt.size(); //iSize == 3; bool bEmpty = vecInt.empty(); // bEmpty == false; 執行vecInt.resize(5); //此時裡面包含1,2,3,0,0元素。 再執行vecInt.resize(8,3); //此時裡面包含1,2,3,0,0,3,3,3元素。 再執行vecInt.resize(2); //此時裡面包含1,2元素。 vector vecInt.push_back(1); //在容器尾部加入一個元素 vecInt.push_back(3); //移除容器中最後一個元素 vecInt.push_back(5); vecInt.push_back(7); vecInt.push_back(9); vecInt.pop_back(); vecInt.pop_back(); //{5 ,7 ,9} 理論知識 vec.at(idx); //返回索引idx所指的數據,如果idx越界,拋出out_of_range異常。 vec[idx]; //返回索引idx所指的數據,越界時,運行直接報錯 vector vecInt.at(2) == vecInt[2] ; //5 vecInt.at(2) = 8; 或 vecInt[2] = 8; vecInt 就包含 1, 3, 8, 7, 9值 int iF = vector.front(); //iF==1 int iB = vector.back(); //iB==9 vector.front() = 11; //vecInt包含{11,3,8,7,9} vector.back() = 19; //vecInt包含{11,3,8,7,19} 2 迭代器是一個“可遍歷STL容器內全部或部分元素”的對象。 2 迭代器指出容器中的一個特定位置。 2 迭代器就如同一個指針。 2 迭代器提供對一個容器中的對象的訪問方法,並且可以定義了容器中對象的范圍。 2 這裡大概介紹一下迭代器的類別。 輸入迭代器:也有叫法稱之為“只讀迭代器”,它從容器中讀取元素,只能一次讀入一個元素向前移動,只支持一遍算法,同一個輸入迭代器不能兩遍遍歷一個序列。 輸出迭代器:也有叫法稱之為“只寫迭代器”,它往容器中寫入元素,只能一次寫入一個元素向前移動,只支持一遍算法,同一個輸出迭代器不能兩遍遍歷一個序列。 正向迭代器:組合輸入迭代器和輸出迭代器的功能,還可以多次解析一個迭代器指定的位置,可以對一個值進行多次讀/寫。 雙向迭代器:組合正向迭代器的功能,還可以通過--操作符向後移動位置。 隨機訪問迭代器:組合雙向迭代器的功能,還可以向前向後跳過任意個位置,可以直接訪問容器中任何位置的元素。 2 目前本系列教程所用到的容器,都支持雙向迭代器或隨機訪問迭代器,下面將會詳細介紹這兩個類別的迭代器。 雙向迭代器支持的操作: it++,++it, it--, --it,*it, itA = itB, itA == itB,itA != itB 其中list,set,multiset,map,multimap支持雙向迭代器。 隨機訪問迭代器支持的操作: 在雙向迭代器的操作基礎上添加 it+=i, it-=i, it+i(或it=it+i),it[i], itA 其中vector,deque支持隨機訪問迭代器。 vector vector it = vecInt.begin(); // *it == 1 ++it; //或者it++; *it == 3,前++的效率比後++的效率高,前++返回引用,後++返回值。 it += 2; //*it== 7 it = it+1; //*it== 9 ++it; //it == vecInt.end(); 此時不能再執行*it,會出錯! 正向遍歷: for(vector { int iItem = *it; cout << iItem; //或直接使用 cout << *it; } 這樣子便打印出1 3 5 7 9 逆向遍歷: for(vector { intiItem = *rit; cout << iItem; //或直接使用cout<< *rit; } 此時將打印出9,7,5,3,1 注意,這裡迭代器的聲明采用vector 迭代器還有其它兩種聲明方法: vector 以上兩種分別是vector 備注:不過容器中的insert和erase方法僅接受這四種類型中的iterator,其它三種不支持。《Effective STL》建議我們盡量使用iterator取代const_iterator、reverse_iterator和const_reverse_iterator。 理論知識 2 vector.insert(pos,elem); //在pos位置插入一個elem元素的拷貝,返回新數據的位置。 2 vector.insert(pos,n,elem);//在pos位置插入n個elem數據,無返回值。 2 vector.insert(pos,beg,end);//在pos位置插入[beg,end)區間的數據,無返回值 簡單案例 vector vector vecA.push_back(1); vecA.push_back(3); vecA.push_back(5); vecA.push_back(7); vecA.push_back(9); vecB.push_back(2); vecB.push_back(4); vecB.push_back(6); vecB.push_back(8); vecA.insert(vecA.begin(),11); //{11, 1, 3, 5, 7,9} vecA.insert(vecA.begin()+1,2,33); //{11,33,33,1,3,5,7,9} vecA.insert(vecA.begin(), vecB.begin() , vecB.end() ); //{2,4,6,8,11,33,33,1,3,5,7,9} 理論知識 2 vector.clear(); //移除容器的所有數據 2 vec.erase(beg,end); //刪除[beg,end)區間的數據,返回下一個數據的位置。 2 vec.erase(pos); //刪除pos位置的數據,返回下一個數據的位置。 簡單案例: 刪除區間內的元素 vecInt是用vector vector vector vecInt.erase(itBegin,itEnd); //此時容器vecInt包含按順序的1,6,9三個元素。 假設 vecInt 包含1,3,2,3,3,3,4,3,5,3,刪除容器中等於3的元素 for(vector { if(*it == 3) { it = vecInt.erase(it); //以迭代器為參數,刪除元素3,並把數據刪除後的下一個元素位置返回給迭代器。 //此時,不執行 ++it; } else { ++it; } } //刪除vecInt的所有元素 vecInt.clear(); //容器為空 這一講,主要講解如下要點: 容器的簡介,容器的分類,各個容器的數據結構 vector,deque,list,set,multiset,map,multimap 容器vector的具體用法(包括迭代器的具體用法)。 vertor簡介,vector使用之前的准備,vector對象的默認構造,vector末尾的添加移除操作,vector的數據存取,迭代器的簡介,雙向迭代器與隨機訪問迭代器 vector與迭代器的配合使用,vector對象的帶參數構造,vector的賦值,vector的大小,vector的插入,vector的刪除。 2 deque是“double-ended queue”的縮寫,和vector一樣都是STL的容器,deque是雙端數組,而vector是單端的。 2 deque在接口上和vector非常相似,在許多操作的地方可以直接替換。 2 deque可以隨機存取元素(支持索引值直接存取,用[]操作符或at()方法,這個等下會詳講)。 2 deque頭部和尾部添加或移除元素都非常快速。但是在中部安插元素或移除元素比較費時。 2 #include deque采用模板類實現,deque對象的默認構造形式:deque deque deque deque ... //尖括號內還可以設置指針類型或自定義類型。 理論知識: 2 deque.push_back(elem); //在容器尾部添加一個數據 2 deque.push_front(elem); //在容器頭部插入一個數據 2 deque.pop_back(); //刪除容器最後一個數據 2 deque.pop_front(); //刪除容器第一個數據 deque deqInt.push_back(1); deqInt.push_back(3); deqInt.push_back(5); deqInt.push_back(7); deqInt.push_back(9); deqInt.pop_front(); deqInt.pop_front(); deqInt.push_front(11); deqInt.push_front(13); deqInt.pop_back(); deqInt.pop_back(); //deqInt{ 13,11,5} 理論知識: 2 deque.at(idx); //返回索引idx所指的數據,如果idx越界,拋出out_of_range。 2 deque[idx]; //返回索引idx所指的數據,如果idx越界,不拋出異常,直接出錯。 2 deque.front(); //返回第一個數據。 2 deque.back(); //返回最後一個數據 deque deqInt.push_back(1); deqInt.push_back(3); deqInt.push_back(5); deqInt.push_back(7); deqInt.push_back(9); intiA = deqInt.at(0); //1 intiB = deqInt[1]; //3 deqInt.at(0)= 99; //99 deqInt[1]= 88; //88 intiFront = deqInt.front(); //99 intiBack = deqInt.back(); //9 deqInt.front()= 77; //77 deqInt.back()= 66; //66 理論知識 2 deque.begin(); //返回容器中第一個元素的迭代器。 2 deque.end(); //返回容器中最後一個元素之後的迭代器。 2 deque.rbegin(); //返回容器中倒數第一個元素的迭代器。 2 deque.rend(); //返回容器中倒數最後一個元素之後的迭代器。 deque deqInt.push_back(1); deqInt.push_back(3); deqInt.push_back(5); deqInt.push_back(7); deqInt.push_back(9); for(deque { cout<< *it; cout<< ""; } //1 3 5 7 9 for(deque { cout<< *rit; cout<< ""; } //97 5 3 1 理論知識 2 deque(beg,end); //構造函數將[beg,end)區間中的元素拷貝給本身。注意該區間是左閉右開的區間。 2 deque(n,elem); //構造函數將n個elem拷貝給本身。 2 deque(const deque&deq); //拷貝構造函數。 deque deqIntA.push_back(1); deqIntA.push_back(3); deqIntA.push_back(5); deqIntA.push_back(7); deqIntA.push_back(9); deque deque deque 理論知識 2 deque.assign(beg,end); //將[beg, end)區間中的數據拷貝賦值給本身。注意該區間是左閉右開的區間。 2 deque.assign(n,elem); //將n個elem拷貝賦值給本身。 2 deque& operator=(const deque &deq); //重載等號操作符 2 deque.swap(deq); // 將vec與本身的元素互換 deque deqIntA.push_back(1); deqIntA.push_back(3); deqIntA.push_back(5); deqIntA.push_back(7); deqIntA.push_back(9); deqIntB.assign(deqIntA.begin(),deqIntA.end()); // 1 3 5 7 9 deqIntC.assign(5,8); //88 8 8 8 deqIntD= deqIntA; //13 5 7 9 deqIntC.swap(deqIntD); //互換 理論知識 2 deque.size(); //返回容器中元素的個數 2 deque.empty(); //判斷容器是否為空 2 deque.resize(num); //重新指定容器的長度為num,若容器變長,則以默認值填充新位置。如果容器變短,則末尾超出容器長度的元素被刪除。 2 deque.resize(num, elem); //重新指定容器的長度為num,若容器變長,則以elem值填充新位置。如果容器變短,則末尾超出容器長度的元素被刪除。 deque deqIntA.push_back(1); deqIntA.push_back(3); deqIntA.push_back(5); intiSize = deqIntA.size(); //3 if(!deqIntA.empty()) { deqIntA.resize(5); //1 3 5 0 0 deqIntA.resize(7,1); //1 3 5 0 0 1 1 deqIntA.resize(2); //1 3 } 理論知識 2 deque.insert(pos,elem); //在pos位置插入一個elem元素的拷貝,返回新數據的位置。 2 deque.insert(pos,n,elem); //在pos位置插入n個elem數據,無返回值。 2 deque.insert(pos,beg,end);//在pos位置插入[beg,end)區間的數據,無返回值。 deque deque deqA.push_back(1); deqA.push_back(3); deqA.push_back(5); deqA.push_back(7); deqA.push_back(9); deqB.push_back(2); deqB.push_back(4); deqB.push_back(6); deqB.push_back(8); deqA.insert(deqA.begin(),11); //{11, 1, 3, 5, 7,9} deqA.insert(deqA.begin()+1,2,33); //{11,33,33,1,3,5,7,9} deqA.insert(deqA.begin(), deqB.begin() , deqB.end() ); //{2,4,6,8,11,33,33,1,3,5,7,9} 理論知識 2 deque.clear(); //移除容器的所有數據 2 deque.erase(beg,end); //刪除[beg,end)區間的數據,返回下一個數據的位置。 2 deque.erase(pos); //刪除pos位置的數據,返回下一個數據的位置。 刪除區間內的元素 deqInt是用deque deque deque deqInt.erase(itBegin,itEnd); //此時容器deqInt包含按順序的1,6,9三個元素。 假設 deqInt 包含1,3,2,3,3,3,4,3,5,3,刪除容器中等於3的元素 for(deque { if(*it == 3) { it = deqInt.erase(it); //以迭代器為參數,刪除元素3,並把數據刪除後的下一個元素位置返回給迭代器。 //此時,不執行 ++it; } else { ++it; } } //刪除deqInt的所有元素 deqInt.clear(); //容器為空 Stack簡介 2 stack是堆棧容器,是一種“先進後出”的容器。 2 stack是簡單地裝飾deque容器而成為另外的一種容器。 2 #include stack采用模板類實現, stack對象的默認構造形式:stack stack stack stack ... //尖括號內還可以設置指針類型或自定義類型。 stack.push(elem); //往棧頭添加元素 stack.pop(); //從棧頭移除第一個元素 stack stkInt.push(1);stkInt.push(3);stkInt.pop(); stkInt.push(5);stkInt.push(7); stkInt.push(9);stkInt.pop(); stkInt.pop(); 此時stkInt存放的元素是1,5 stack(const stack &stk); //拷貝構造函數 stack& operator=(const stack &stk); //重載等號操作符 stack stkIntA.push(1); stkIntA.push(3); stkIntA.push(5); stkIntA.push(7); stkIntA.push(9); stack stack stkIntC= stkIntA; //賦值 2 stack.top(); //返回最後一個壓入棧元素 stack stkIntA.push(1); stkIntA.push(3); stkIntA.push(5); stkIntA.push(7); stkIntA.push(9); intiTop = stkIntA.top(); //9 stkIntA.top()= 19; //19 2 stack.empty(); //判斷堆棧是否為空 2 stack.size(); //返回堆棧的大小 stack stkIntA.push(1); stkIntA.push(3); stkIntA.push(5); stkIntA.push(7); stkIntA.push(9); if(!stkIntA.empty()) { intiSize = stkIntA.size(); //5 } 2 queue是隊列容器,是一種“先進先出”的容器。 2 queue是簡單地裝飾deque容器而成為另外的一種容器。 2 #include queue采用模板類實現,queue對象的默認構造形式:queue queue queue queue ... //尖括號內還可以設置指針類型或自定義類型。 queue.push(elem); //往隊尾添加元素 queue.pop(); //從隊頭移除第一個元素 queue queInt.push(1);queInt.push(3); queInt.push(5);queInt.push(7); queInt.push(9);queInt.pop(); queInt.pop(); 此時queInt存放的元素是5,7,9 queue(const queue &que); //拷貝構造函數 queue& operator=(const queue &que); //重載等號操作符 queue queIntA.push(1); queIntA.push(3); queIntA.push(5); queIntA.push(7); queIntA.push(9); queue queue queIntC= queIntA; //賦值 2 queue.back(); //返回最後一個元素 2 queue.front(); //返回第一個元素 queue queIntA.push(1); queIntA.push(3); queIntA.push(5); queIntA.push(7); queIntA.push(9); intiFront = queIntA.front(); //1 intiBack = queIntA.back(); //9 queIntA.front()= 11; //11 queIntA.back()= 19; //19 2 queue.empty(); //判斷隊列是否為空 2 queue.size(); //返回隊列的大小 queue queIntA.push(1); queIntA.push(3); queIntA.push(5); queIntA.push(7); queIntA.push(9); if(!queIntA.empty()) { intiSize = queIntA.size(); //5 } 2 list是一個雙向鏈表容器,可高效地進行插入刪除元素。 2 list不可以隨機存取元素,所以不支持at.(pos)函數與[]操作符。It++(ok) it+5(err) 2 #include list采用采用模板類實現,對象的默認構造形式:list list list list ... //尖括號內還可以設置指針類型或自定義類型。 2 list.push_back(elem); //在容器尾部加入一個元素 2 list.pop_back(); //刪除容器中最後一個元素 2 list.push_front(elem); //在容器開頭插入一個元素 2 list.pop_front();//從容器開頭移除第一個元素 list lstInt.push_back(1); lstInt.push_back(3); lstInt.push_back(5); lstInt.push_back(7); lstInt.push_back(9); lstInt.pop_front(); lstInt.pop_front(); lstInt.push_front(11); lstInt.push_front(13); lstInt.pop_back(); lstInt.pop_back(); // lstInt{13,11,5} 2 list.front(); //返回第一個元素。 2 list.back(); //返回最後一個元素。 list lstInt.push_back(1); lstInt.push_back(3); lstInt.push_back(5); lstInt.push_back(7); lstInt.push_back(9); intiFront = lstInt.front(); //1 intiBack = lstInt.back(); //9 lstInt.front()= 11; //11 lstInt.back()= 19; //19 2 list.begin();//返回容器中第一個元素的迭代器。 2 list.end();//返回容器中最後一個元素之後的迭代器。 2 list.rbegin(); //返回容器中倒數第一個元素的迭代器。 2 list.rend(); //返回容器中倒數最後一個元素的後面的迭代器。 list lstInt.push_back(1); lstInt.push_back(3); lstInt.push_back(5); lstInt.push_back(7); lstInt.push_back(9); for(list { cout<< *it; cout<< " "; } for(list { cout<< *rit; cout<< " "; } 2 list(beg,end); //構造函數將[beg,end)區間中的元素拷貝給本身。注意該區間是左閉右開的區間。 2 list(n,elem); //構造函數將n個elem拷貝給本身。 2 list(const list &lst); //拷貝構造函數。 list lstIntA.push_back(1); lstIntA.push_back(3); lstIntA.push_back(5); lstIntA.push_back(7); lstIntA.push_back(9); list list list 2 list.assign(beg,end); //將[beg, end)區間中的數據拷貝賦值給本身。注意該區間是左閉右開的區間。 2 list.assign(n,elem); //將n個elem拷貝賦值給本身。 2 list& operator=(const list &lst); //重載等號操作符 2 list.swap(lst); // 將lst與本身的元素互換。 list lstIntA.push_back(1); lstIntA.push_back(3); lstIntA.push_back(5); lstIntA.push_back(7); lstIntA.push_back(9); lstIntB.assign(lstIntA.begin(),lstIntA.end()); //1 3 5 7 9 lstIntC.assign(5,8); //88 8 8 8 lstIntD= lstIntA; //13 5 7 9 lstIntC.swap(lstIntD); //互換 2 list.size(); //返回容器中元素的個數 2 list.empty(); //判斷容器是否為空 2 list.resize(num); //重新指定容器的長度為num,若容器變長,則以默認值填充新位置。如果容器變短,則末尾超出容器長度的元素被刪除。 2 list.resize(num, elem); //重新指定容器的長度為num,若容器變長,則以elem值填充新位置。如果容器變短,則末尾超出容器長度的元素被刪除。 list lstIntA.push_back(1); lstIntA.push_back(3); lstIntA.push_back(5); if(!lstIntA.empty()) { intiSize = lstIntA.size(); //3 lstIntA.resize(5); //1 3 5 0 0 lstIntA.resize(7,1); //1 3 5 0 0 1 1 lstIntA.resize(2); //1 3 } 2 list.insert(pos,elem); //在pos位置插入一個elem元素的拷貝,返回新數據的位置。 2 list.insert(pos,n,elem); //在pos位置插入n個elem數據,無返回值。 2 list.insert(pos,beg,end); //在pos位置插入[beg,end)區間的數據,無返回值。 list list lstA.push_back(1); lstA.push_back(3); lstA.push_back(5); lstA.push_back(7); lstA.push_back(9); lstB.push_back(2); lstB.push_back(4); lstB.push_back(6); lstB.push_back(8); lstA.insert(lstA.begin(),11); //{11, 1, 3, 5, 7, 9} lstA.insert(++lstA.begin(),2,33); //{11,33,33,1,3,5,7,9} lstA.insert(lstA.begin(), lstB.begin() , lstB.end() ); //{2,4,6,8,11,33,33,1,3,5,7,9} 2 list.clear(); //移除容器的所有數據 2 list.erase(beg,end); //刪除[beg,end)區間的數據,返回下一個數據的位置。 2 list.erase(pos); //刪除pos位置的數據,返回下一個數據的位置。 2 lst.remove(elem); //刪除容器中所有與elem值匹配的元素。 刪除區間內的元素 lstInt是用list list ++ itBegin; list ++ itEnd; ++ itEnd; ++ itEnd; lstInt.erase(itBegin,itEnd); //此時容器lstInt包含按順序的1,6,9三個元素。 假設 lstInt 包含1,3,2,3,3,3,4,3,5,3,刪除容器中等於3的元素的方法一 for(list { if(*it == 3) { it = lstInt.erase(it); //以迭代器為參數,刪除元素3,並把數據刪除後的下一個元素位置返回給迭代器。 //此時,不執行 ++it; } else { ++it; } } 刪除容器中等於3的元素的方法二 lstInt.remove(3); 刪除lstInt的所有元素 lstInt.clear(); //容器為空 2 lst.reverse(); //反轉鏈表,比如lst包含1,3,5元素,運行此方法後,lst就包含5,3,1元素。 list lstA.push_back(1); lstA.push_back(3); lstA.push_back(5); lstA.push_back(7); lstA.push_back(9); lstA.reverse(); //9 7 5 3 1 2 一、容器deque的使用方法 適合 在頭尾添加移除元素。使用方法與vector類似。 2 二、容器queue,stack的使用方法 適合隊列,堆棧的操作方式。 2 三、容器list的使用方法 適合在任意位置快速插入移除元素 v 最大值優先級隊列、最小值優先級隊列 v 優先級隊列適配器 STLpriority_queue v 用來開發一些特殊的應用,請對stl的類庫,多做擴展性學習 priority_queue priority_queue pq.empty() pq.size() pq.top() pq.pop() pq.push(item) #include using namespace std; #include "queue" void main81() { priority_queue //priority_queue priority_queue p1.push(33); p1.push(11); p1.push(55); p1.push(22); cout<<"隊列大小" << p1.size() << endl; cout<<"隊頭" << p1.top() << endl; while(p1.size() > 0) { cout<< p1.top() << " "; p1.pop(); } cout<< endl; cout<< "測試最小值優先級隊列" << endl; p2.push(33); p2.push(11); p2.push(55); p2.push(22); while(p2.size() > 0) { cout<< p2.top() << " "; p2.pop(); } } 2 set是一個集合容器,其中所包含的元素是唯一的,集合中的元素按一定的順序排列。元素插入過程是按排序規則插入,所以不能指定插入位置。 2 set采用紅黑樹變體的數據結構實現,紅黑樹屬於平衡二叉樹。在插入操作和刪除操作上比vector快。 2 set不可以直接存取元素。(不可以使用at.(pos)與[]操作符)。 2 multiset與set的區別:set支持唯一鍵值,每個元素值只能出現一次;而multiset中同一值可以出現多次。 2 不可以直接修改set或multiset容器中的元素值,因為該類容器是自動排序的。如果希望修改一個元素值,必須先刪除原有的元素,再插入新的元素。 2 #include set set set multiset multi set multi set 2 set.insert(elem); //在容器中插入元素。 2 set.begin(); //返回容器中第一個數據的迭代器。 2 set.end(); //返回容器中最後一個數據之後的迭代器。 2 set.rbegin(); //返回容器中倒數第一個元素的迭代器。 2 set.rend(); //返回容器中倒數最後一個元素的後面的迭代器。 set setInt.insert(3);setInt.insert(1);setInt.insert(5);setInt.insert(2); for(set { int iItem = *it; cout << iItem; //或直接使用cout<< *it } //這樣子便順序輸出 1 2 3 5。 set.rbegin()與set.rend()。略。 2 set 2 set 2 set 2 less 2 疑問1:less<>與greater<>是什麼? 2 疑問2:如果set<>不包含int類型,而是包含自定義類型,set容器如何排序? 2 要解決如上兩個問題,需要了解容器的函數對象,也叫偽函數,英文名叫functor。 2 下面將講解什麼是functor,functor的用法。 使用stl提供的函數對象 set setIntB.insert(3); setIntB.insert(1); setIntB.insert(5); setIntB.insert(2); 此時容器setIntB就包含了按順序的5,3,2,1元素 2 盡管函數指針被廣泛用於實現函數回調,但C++還提供了一個重要的實現回調函數的方法,那就是函數對象。 2 functor,翻譯成函數對象,偽函數,算符,是重載了“()”操作符的普通類對象。從語法上講,它與普通函數行為類似。 2 greater<>與less<>就是函數對象。 2 下面舉出greater 下面舉出greater struct greater { bool operator()(const int& iLeft, const int& iRight) { return (iLeft>iRight); //如果是實現less } } 容器就是調用函數對象的operator()方法去比較兩個值的大小。 題目:學生包含學號,姓名屬性,現要求任意插入幾個學生對象到set容器中,使得容器中的學生按學號的升序排序。 解: //學生類 class CStudent { public: CStudent(intiID, string strName) { m_iID= iID; m_strName= strName; } int m_iID; //學號 string m_strName; //姓名 } //為保持主題鮮明,本類不寫拷貝構造函數,不類也不需要寫拷貝構造函數。但大家仍要有考慮拷貝構造函數的習慣。 //函數對象 struct StuFunctor { booloperator() (const CStudent &stu1,const CStudent &stu2) { return(stu1.m_iID } } //main函數 void main() { set setStu.insert(CStudent(3,"小張")); setStu.insert(CStudent(1,"小李")); setStu.insert(CStudent(5,"小王")); setStu.insert(CStudent(2,"小劉")); //此時容器setStu包含了四個學生對象,分別是按姓名順序的“小李”,“小劉”,“小張”,“小王” } set(const set &st); //拷貝構造函數 set& operator=(const set &st); //重載等號操作符 set.swap(st); //交換兩個集合容器 set setIntA.insert(3); setIntA.insert(1); setIntA.insert(7); setIntA.insert(5); setIntA.insert(9); set set setIntC= setIntA; //1 3 5 7 9 setIntC.insert(6); setIntC.swap(setIntA); //交換 2 set.size(); //返回容器中元素的數目 2 set.empty();//判斷容器是否為空 set setIntA.insert(3); setIntA.insert(1); setIntA.insert(7); setIntA.insert(5); setIntA.insert(9); if(!setIntA.empty()) { intiSize = setIntA.size(); //5 } 2 set.clear(); //清除所有元素 2 set.erase(pos); //刪除pos迭代器所指的元素,返回下一個元素的迭代器。 2 set.erase(beg,end); //刪除區間[beg,end)的所有元素 ,返回下一個元素的迭代器。 2 set.erase(elem); //刪除容器中值為elem的元素。 刪除區間內的元素 setInt是用set set ++ itBegin; set ++ itEnd; ++ itEnd; ++ itEnd; setInt.erase(itBegin,itEnd); //此時容器setInt包含按順序的1,6,9,11四個元素。 刪除容器中第一個元素 setInt.erase(setInt.begin()); //6,9,11 刪除容器中值為9的元素 set.erase(9); 刪除setInt的所有元素 setInt.clear(); //容器為空 2 set.find(elem); //查找elem元素,返回指向elem元素的迭代器。 2 set.count(elem); //返回容器中值為elem的元素個數。對set來說,要麼是0,要麼是1。對multiset來說,值可能大於1。 2 set.lower_bound(elem); //返回第一個>=elem元素的迭代器。 2 set.upper_bound(elem); // 返回第一個>elem元素的迭代器。 2 set.equal_range(elem); //返回容器中與elem相等的上下限的兩個迭代器。上限是閉區間,下限是開區間,如[beg,end)。 2 2 以上函數返回兩個迭代器,而這兩個迭代器被封裝在pair中。 2 以下講解pair的含義與使用方法。 2 set setInt.insert(3); setInt.insert(1); setInt.insert(7); setInt.insert(5); setInt.insert(9); set intiA = *itA; //iA == 5 intiCount = setInt.count(5); //iCount == 1 set set intiB = *itB; //iB == 5 intiC = *itC; //iC == 7 pair 2 pair譯為對組,可以將兩個值視為一個單元。 2 pair 2pair.first是pair裡面的第一個值,是T1類型。 2pair.second是pair裡面的第二個值,是T2類型。 set ...//往setInt容器插入元素1,3,5,7,9 pair< set set set //此時 *itBeg==5 而 *itEnd == 7 2 一、容器set/multiset的使用方法; 紅黑樹的變體,查找效率高,插入不能指定位置,插入時自動排序。 2 二、functor的使用方法; 類似於函數的功能,可用來自定義一些規則,如元素比較規則。 2 三、pair的使用方法。 對組,一個整體的單元,存放兩個類型(T1,T2,T1可與T2一樣)的兩個元素。 案例: int x; scanf("%ld",&x); multiset while(x!=0){ h.insert(x);//將x插入h中 scanf("%ld",&x); } pair< multiset multiset multiset int nBeg= *itBeg; int nEnd= *itEnd; while(!h.empty()){// 序列非空h.empty()==true時表示h已經空了 multiset printf("%ld",*c);//將地址c存的數據輸出 h.erase(c);//從h序列中將c指向的元素刪除 } 2 map是標准的關聯式容器,一個map是一個鍵值對序列,即(key,value)對。它提供基於key的快速檢索能力。 2 map中key值是唯一的。集合中的元素按一定的順序排列。元素插入過程是按排序規則插入,所以不能指定插入位置。 2 map的具體實現采用紅黑樹變體的平衡二叉樹的數據結構。在插入操作和刪除操作上比vector快。 2 map可以直接存取key所對應的value,支持[]操作符,如map[key]=value。 2 multimap與map的區別:map支持唯一鍵值,每個鍵只能出現一次;而multimap中相同鍵可以出現多次。multimap不支持[]操作符。 2 #include map/multimap對象的默認構造 map/multimap采用模板類實現,對象的默認構造形式: map multimap 如: map map //其中T1,T2還可以用各種指針類型或自定義類型 map的插入與迭代器 2 map.insert(...); //往容器插入元素,返回pair 2 在map中插入元素的三種方式: 假設 map 2 一、通過pair的方式插入對象 mapStu.insert( pair 2 二、通過pair的方式插入對象 mapStu.inset(make_pair(-1,“校長-1”)); 2 三、通過value_type的方式插入對象 mapStu.insert( map 2 四、通過數組的方式插入值 mapStu[3] = “小劉"; mapStu[5] = “小王"; 2 前三種方法,采用的是insert()方法,該方法返回值為pair 2 第四種方法非常直觀,但存在一個性能的問題。插入3時,先在mapStu中查找主鍵為3的項,若沒發現,則將一個鍵為3,值為初始化值的對組插入到mapStu中,然後再將值修改成“小劉”。若發現已存在3這個鍵,則修改這個鍵對應的value。 2 string strName = mapStu[2];//取操作或插入操作 2 只有當mapStu存在2這個鍵時才是正確的取操作,否則會自動插入一個實例,鍵為2,值為初始化值。 假設 map pair< map int iFirstFirst =(pairResult.first)->first; //iFirst== 3; string strFirstSecond =(pairResult.first)->second; //strFirstSecond為"小張" bool bSecond = pairResult.second; //bSecond== true; mapA.insert(map mapA[3] = "小劉"; //修改value mapA[5] = "小王"; //插入方式三 string str1 = mapA[2]; //執行插入 string()操作,返回的str1的字符串內容為空。 string str2 = mapA[3]; //取得value,str2為"小劉" //迭代器遍歷 for(map { pair intiKey = pr.first; stringstrValue = pr.second; } map.rbegin()與map.rend() 略。 2 map 2 map 2 less 2 可編寫自定義函數對象以進行自定義類型的比較,使用方法與set構造時所用的函數對象一樣。 2 map.begin(); //返回容器中第一個數據的迭代器。 2 map.end(); //返回容器中最後一個數據之後的迭代器。 2 map.rbegin(); //返回容器中倒數第一個元素的迭代器。 2 map.rend(); //返回容器中倒數最後一個元素的後面的迭代器。 map(const map &mp); //拷貝構造函數 map& operator=(const map &mp); //重載等號操作符 map.swap(mp); //交換兩個集合容器 例如: map mapA.insert(pair mapA.insert(pair mapA.insert(pair mapA.insert(pair map map mapC= mapA; //賦值 mapC[3]= "老張"; mapC.swap(mapA); //交換 2 map.size(); //返回容器中元素的數目 2 map.empty();//判斷容器是否為空 map mapA.insert(pair mapA.insert(pair mapA.insert(pair mapA.insert(pair if(mapA.empty()) { intiSize = mapA.size(); //iSize== 4 } 2 map.clear(); //刪除所有元素 2 map.erase(pos); //刪除pos迭代器所指的元素,返回下一個元素的迭代器。 2 map.erase(beg,end); //刪除區間[beg,end)的所有元素 ,返回下一個元素的迭代器。 2 map.erase(keyElem); //刪除容器中key為keyElem的對組。 map mapA.insert(pair mapA.insert(pair mapA.insert(pair mapA.insert(pair //刪除區間內的元素 map ++itBegin; ++itBegin; map mapA.erase(itBegin,itEnd); //此時容器mapA包含按順序的{1,"小楊"}{3,"小張"}兩個元素。 mapA.insert(pair mapA.insert(pair //刪除容器中第一個元素 mapA.erase(mapA.begin()); //此時容器mapA包含了按順序的{3,"小張"}{5,"小王"}{7,"小趙"}三個元素 //刪除容器中key為5的元素 mapA.erase(5); //刪除mapA的所有元素 mapA.clear(); //容器為空 2 map.find(key); 查找鍵key是否存在,若存在,返回該鍵的元素的迭代器;若不存在,返回map.end(); 2 map.count(keyElem); //返回容器中key為keyElem的對組個數。對map來說,要麼是0,要麼是1。對multimap來說,值可能大於1。 map if(it == mapStu.end()) { //沒找到 } else { //找到了 pair int iID = pairStu.first; //或 int iID = it->first; string strName = pairStu.second; //或 string strName = it->second; } 2 map.lower_bound(keyElem); //返回第一個key>=keyElem元素的迭代器。 2 map.upper_bound(keyElem); // 返回第一個key>keyElem元素的迭代器。 例如: mapStu是用map it = mapStu.lower_bound(5); //it->first==5 it->second=="小王" it = mapStu.upper_bound(5); //it->first==7 it->second=="小趙" it = mapStu.lower_bound(6); //it->first==7 it->second=="小趙" it = mapStu.upper_bound(6); //it->first==7 it->second=="小趙" 2 map.equal_range(keyElem); //返回容器中key與keyElem相等的上下限的兩個迭代器。上限是閉區間,下限是開區間,如[beg,end)。 以上函數返回兩個迭代器,而這兩個迭代器被封裝在pair中。 例如 map ... //往mapStu容器插入元素{1,"小李"}{3,"小張"}{5,"小王"}{7,"小趙"}{9,"小陳"} pair< map map map //此時 itBeg->first==5 , itEnd->first == 7, itBeg->second=="小王", itEnd->second=="小趙" Multimap 案例: //1個key值可以對應多個valude =è分組 //公司有銷售部 sale (員工2名)、技術研發部 development (1人)、財務部 Financial (2人) //人員信息有:姓名,年齡,電話、工資等組成 //通過 multimap進行 信息的插入、保存、顯示 //分部門顯示員工信息 C++模板是容器的概念。 理論提高:所有容器提供的都是值(value)語意,而非引用(reference)語意。容器執行插入元素的操作時,內部實施拷貝動作。所以STL容器內存儲的元素必須能夠被拷貝(必須提供拷貝構造函數)。 2 除了queue與stack外,每個容器都提供可返回迭代器的函數,運用返回的迭代器就可以訪問元素。 2 通常STL不會丟出異常。要求使用者確保傳入正確的參數。 2 每個容器都提供了一個默認構造函數跟一個默認拷貝構造函數。 2 如已有容器vecIntA。 2 vector 2 與大小相關的操作方法(c代表容器): c.size(); //返回容器中元素的個數 c.empty(); //判斷容器是否為空 2 比較操作(c1,c2代表容器): c1 == c2 判斷c1是否等於c2 c1 != c2 判斷c1是否不等於c2 c1 = c2 把c2的所有元素指派給c1 2 Vector的使用場景:比如軟件歷史操作記錄的存儲,我們經常要查看歷史記錄,比如上一次的記錄,上上次的記錄,但卻不會去刪除記錄,因為記錄是事實的描述。 2 deque的使用場景:比如排隊購票系統,對排隊者的存儲可以采用deque,支持頭端的快速移除,尾端的快速添加。如果采用vector,則頭端移除時,會移動大量的數據,速度慢。 2 vector與deque的比較: 2 一:vector.at()比deque.at()效率高,比如vector.at(0)是固定的,deque的開始位置卻是不固定的。 2 二:如果有大量釋放操作的話,vector花的時間更少,這跟二者的內部實現有關。 2 三:deque支持頭部的快速插入與快速移除,這是deque的優點。 2 list的使用場景:比如公交車乘客的存儲,隨時可能有乘客下車,支持頻繁的不確實位置元素的移除插入。 2 set的使用場景:比如對手機游戲的個人得分記錄的存儲,存儲要求從高分到低分的順序排列。 2 map的使用場景:比如按ID號存儲十萬個用戶,想要快速要通過ID查找對應的用戶。二叉樹的查找效率,這時就體現出來了。如果是vector容器,最壞的情況下可能要遍歷完整個容器才能找到該用戶。 2 算法部分主要由頭文件 2 2 2 2 STL提供了大量實現算法的模版函數,只要我們熟悉了STL之後,許多代碼可以被大大的化簡,只需要通過調用一兩個算法模板,就可以完成所需要的功能,從而大大地提升效率。 2 #include 2 #include 2 #include 函數名 頭文件 函數功能 adjacent_find 在iterator對標識元素范圍內,查找一對相鄰重復元素,找到則返回指向這對元素的第一個元素的ForwardIterator.否則返回last.重載版本使用輸入的二元操作符代替相等的判斷 函數原形 template template binary_search 在有序序列中查找value,找到返回true.重載的版本實用指定的比較函數對象或函數指針來判斷相等 函數原形 template template count 利用等於操作符,把標志范圍內的元素與輸入值比較,返回相等元素個數 函數原形 template count_if 利用輸入的操作符,對標志范圍內的元素進行操作,返回結果為true的個數 函數原形 template equal_range 功能類似equal,返回一對iterator,第一個表示lower_bound,第二個表示upper_bound 函數原形 template template find 利用底層元素的等於操作符,對指定范圍內的元素與輸入值進行比較.當匹配時,結束搜索,返回該元素的一個InputIterator 函數原形 template find_end 在指定范圍內查找"由輸入的另外一對iterator標志的第二個序列"的最後一次出現.找到則返回最後一對的第一個ForwardIterator,否則返回輸入的"另外一對"的第一個ForwardIterator.重載版本使用用戶輸入的操作符代替等於操作 函數原形 template template find_first_of 在指定范圍內查找"由輸入的另外一對iterator標志的第二個序列"中任意一個元素的第一次出現。重載版本中使用了用戶自定義操作符 函數原形 template template find_if 使用輸入的函數代替等於操作符執行find template lower_bound 返回一個ForwardIterator,指向在有序序列范圍內的可以插入指定值而不破壞容器順序的第一個位置.重載函數使用自定義比較操作 函數原形 template template upper_bound 返回一個ForwardIterator,指向在有序序列范圍內插入value而不破壞容器順序的最後一個位置,該位置標志一個大於value的值.重載函數使用自定義比較操作 函數原形 template template search 給出兩個范圍,返回一個ForwardIterator,查找成功指向第一個范圍內第一次出現子序列(第二個范圍)的位置,查找失敗指向last1,重載版本使用自定義的比較操作 函數原形 template template search_n 在指定范圍內查找val出現n次的子序列。重載版本使用自定義的比較操作 函數原形 template template 函數名 頭文件 函數功能 make_heap 把指定范圍內的元素生成一個堆。重載版本使用自定義比較操作 函數原形 template template pop_heap 並不真正把最大元素從堆中彈出,而是重新排序堆。它把first和last-1交換,然後重新生成一個堆。可使用容器的back來訪問被"彈出"的元素或者使用pop_back進行真正的刪除。重載版本使用自定義的比較操作 函數原形 template template push_heap 假設first到last-1是一個有效堆,要被加入到堆的元素存放在位置last-1,重新生成堆。在指向該函數前,必須先把元素插入容器後。重載版本使用指定的比較操作 函數原形 template template sort_heap 對指定范圍內的序列重新排序,它假設該序列是個有序堆。重載版本使用自定義比較操作 函數原形 template template 函數名 頭文件 函數功能 equal 如果兩個序列在標志范圍內元素都相等,返回true。重載版本使用輸入的操作符代替默認的等於操作符 函數原形 template template includes 判斷第一個指定范圍內的所有元素是否都被第二個范圍包含,使用底層元素的<操作符,成功返回true。重載版本使用用戶輸入的函數 函數原形 template template lexicographical_compare 比較兩個序列。重載版本使用用戶自定義比較操作 函數原形 template template max 返回兩個元素中較大一個。重載版本使用自定義比較操作 函數原形 template template max_element 返回一個ForwardIterator,指出序列中最大的元素。重載版本使用自定義比較操作 函數原形 template template min 返回兩個元素中較小一個。重載版本使用自定義比較操作 函數原形 template template min_element 返回一個ForwardIterator,指出序列中最小的元素。重載版本使用自定義比較操作 函數原形 template template mismatch 並行比較兩個序列,指出第一個不匹配的位置,返回一對iterator,標志第一個不匹配元素位置。如果都匹配,返回每個容器的last。重載版本使用自定義的比較操作 函數原形 template template 函數名 頭文件 函數功能 set_union 構造一個有序序列,包含兩個序列中所有的不重復元素。重載版本使用自定義的比較操作 函數原形 template template set_intersection 構造一個有序序列,其中元素在兩個序列中都存在。重載版本使用自定義的比較操作 函數原形 template template set_difference 構造一個有序序列,該序列僅保留第一個序列中存在的而第二個中不存在的元素。重載版本使用自定義的比較操作 函數原形 template template set_symmetric_difference 構造一個有序序列,該序列取兩個序列的對稱差集(並集-交集) 函數原形 template template 提供計算給定集合按一定順序的所有可能排列組合 函數名 頭文件 函數功能 next_permutation 取出當前范圍內的排列,並重新排序為下一個排列。重載版本使用自定義的比較操作 函數原形 template template prev_permutation 取出指定范圍內的序列並將它重新排序為上一個序列。如果不存在上一個序列則返回false。重載版本使用自定義的比較操作 函數原形 template template 函數名 頭文件 函數功能 inplace_merge 合並兩個有序序列,結果序列覆蓋兩端范圍。重載版本使用輸入的操作進行排序 函數原形 template template merge 合並兩個有序序列,存放到另一個序列。重載版本使用自定義的比較 函數原形 template template nth_element 將范圍內的序列重新排序,使所有小於第n個元素的元素都出現在它前面,而大於它的都出現在後面。重載版本使用自定義的比較操作 函數原形 template template partial_sort 對序列做部分排序,被排序元素個數正好可以被放到范圍內。重載版本使用自定義的比較操作 函數原形 template template partial_sort_copy 與partial_sort類似,不過將經過排序的序列復制到另一個容器 函數原形 template template partition 對指定范圍內元素重新排序,使用輸入的函數,把結果為true的元素放在結果為false的元素之前 函數原形 template random_shuffle 對指定范圍內的元素隨機調整次序。重載版本輸入一個隨機數產生操作 函數原形 template template reverse 將指定范圍內元素重新反序排序 函數原形 template reverse_copy 與reverse類似,不過將結果寫入另一個容器 函數原形 template rotate 將指定范圍內元素移到容器末尾,由middle指向的元素成為容器第一個元素 函數原形 template rotate_copy 與rotate類似,不過將結果寫入另一個容器 函數原形 template sort 以升序重新排列指定范圍內的元素。重載版本使用自定義的比較操作 函數原形 template template stable_sort 與sort類似,不過保留相等元素之間的順序關系 函數原形 template template stable_partition 與partition類似,不過不保證保留容器中的相對順序 函數原形 template 函數名 頭文件 函數功能 copy 復制序列 函數原形 template copy_backward 與copy相同,不過元素是以相反順序被拷貝 函數原形 template iter_swap 交換兩個ForwardIterator的值 函數原形 template remove 刪除指定范圍內所有等於指定元素的元素。注意,該函數不是真正刪除函數。內置函數不適合使用remove和remove_if函數 函數原形 template remove_copy 將所有不匹配元素復制到一個制定容器,返回OutputIterator指向被拷貝的末元素的下一個位置 函數原形 template remove_if 刪除指定范圍內輸入操作結果為true的所有元素 函數原形 template remove_copy_if 將所有不匹配元素拷貝到一個指定容器 函數原形 template replace 將指定范圍內所有等於vold的元素都用vnew代替 函數原形 template replace_copy 與replace類似,不過將結果寫入另一個容器 函數原形 template replace_if 將指定范圍內所有操作結果為true的元素用新值代替 函數原形 template replace_copy_if 與replace_if,不過將結果寫入另一個容器 函數原形 template swap 交換存儲在兩個對象中的值 函數原形 template swap_range 將指定范圍內的元素與另一個序列元素值進行交換 函數原形 template unique 清除序列中重復元素,和remove類似,它也不能真正刪除元素。重載版本使用自定義比較操作 函數原形 template template unique_copy 與unique類似,不過把結果輸出到另一個容器 函數原形 template template 函數名 頭文件 函數功能 fill 將輸入值賦給標志范圍內的所有元素 函數原形 template fill_n 將輸入值賦給first到first+n范圍內的所有元素 函數原形 template for_each 用指定函數依次對指定范圍內所有元素進行迭代訪問,返回所指定的函數類型。該函數不得修改序列中的元素 函數原形 template generate 連續調用輸入的函數來填充指定的范圍 函數原形 template generate_n 與generate函數類似,填充從指定iterator開始的n個元素 函數原形 template transform 將輸入的操作作用與指定范圍內的每個元素,並產生一個新的序列。重載版本將操作作用在一對元素上,另外一個元素來自輸入的另外一個序列。結果輸出到指定容器 函數原形 template template 函數名 頭文件 函數功能 accumulate iterator對標識的序列段元素之和,加到一個由val指定的初始值上。重載版本不再做加法,而是傳進來的二元操作符被應用到元素上 函數原形 template template partial_sum 創建一個新序列,其中每個元素值代表指定范圍內該位置前所有元素之和。重載版本使用自定義操作代替加法 函數原形 template template product 對兩個序列做內積(對應元素相乘,再求和)並將內積加到一個輸入的初始值上。重載版本使用用戶定義的操作 函數原形 template template adjacent_difference 創建一個新序列,新序列中每個新值代表當前元素與上一個元素的差。重載版本用指定二元操作計算相鄰元素的差 函數原形 template template 2 常用的查找算法: adjacent_find()( adjacent 是鄰近的意思),binary_search(),count(), count_if(),equal_range(),find(),find_if()。 2 常用的排序算法: merge(),sort(),random_shuffle()(shuffle是洗牌的意思),reverse()。 2 常用的拷貝和替換算法: copy(), replace(), replace_if(),swap() 2 常用的算術和生成算法: accumulate()( accumulate 是求和的意思),fill(),。 2 常用的集合算法: set_union(),set_intersection(), set_difference()。 2 常用的遍歷算法: for_each(), transform()( transform 是變換的意思) 函數對象: 重載函數調用操作符的類,其對象常稱為函數對象(functionobject),即它們是行為類似函數的對象。一個類對象,表現出一個函數的特征,就是通過“對象名+(參數列表)”的方式使用一個類對象,如果沒有上下文,完全可以把它看作一個函數對待。 這是通過重載類的operator()來實現的。 “在標准庫中,函數對象被廣泛地使用以獲得彈性”,標准庫中的很多算法都可以使用函數對象或者函數來作為自定的回調行為; 謂詞: 一元函數對象:函數參數1個; 二元函數對象:函數參數2個; 一元謂詞函數參數1個,函數返回值是bool類型,可以作為一個判斷式 謂詞可以使一個仿函數,也可以是一個回調函數。 二元謂詞函數參數2個,函數返回值是bool類型 一元謂詞函數舉例如下 1,判斷給出的string對象的長度是否小於6 bool GT6(const string &s) { return s.size()>= 6; } 2,判斷給出的int是否在3到8之間 bool Compare( int i ) { return ( i >=3 && i <= 8 ); } 二元謂詞舉例如下 1,比較兩個string對象,返回一個bool值,指出第一個string是否比第二個短 bool isShorter(const string &s1, conststring &s2) { return s1.size()< s2.size(); } //1普通類 重載 函數調用操作符 template void FuncShowElemt(T &t) //普通函數 不能像 仿函數那樣記錄狀態 { cout<< t << " "; }; void showChar(char &t) { cout<< t << " "; } //函數模板 重載 函數調用操作符 template class ShowElemt { public: ShowElemt() { n= 0; } voidoperator()(T &t) { n++; cout<< t << " "; } voidprintCount() { cout<< n << endl; } public: intn; }; //1 函數對象 基本使用 void main11() { inta = 100; FuncShowElemt ShowElemt showElemt(a);//函數對象調用 } //1元謂詞 例子 template class Isdiv { public: Isdiv(constT &divisor) // { this->divisor= divisor; } booloperator()(T &t) { return(t%divisor == 0); } protected: private: Tdivisor; }; void main13() { vector for(int i=10; i<33; i++) { v2.push_back(i); } vector inta = 4; Isdiv //_InIt find_if(_InIt _First, _InIt _Last, _Pr _Pred) //返回的是迭代器 it= find_if(v2.begin(), v2.end(), Isdiv if(it != v2.end()) { cout<< "第一個被4整除的數是:" << *it << endl; } } template struct SumAdd { Toperator()(T &t1, T &t2) { returnt1 + t2; } }; template void printE(T &t) { for(vector { cout<< *it << " "; } } void printVector(vector { for(vector { cout<< *it << " "; } } voidmain14() { vector vector v1.push_back(1); v1.push_back(2); v1.push_back(3); v2.push_back(4); v2.push_back(5); v2.push_back(6); v3.resize(10); //transform(v1.begin(),v1.end(), v2.begin(),v3.begin(), SumAdd /* template class_InIt2, class_OutIt, class_Fn2> inline _OutIttransform(_InIt1 _First1, _InIt1 _Last1, _InIt2_First2, _OutIt _Dest, _Fn2 _Func) */ vector cout<< *it << endl; printE(v3); } void current(int &v) { cout<< v << " "; } bool MyCompare(const int &a, const int&b) { returna < b; } void main15() { vector for(int i=0; i<10; i++) { v[i]= rand() % 100; } for_each(v.begin(),v.end(), current); printf("\n"); sort(v.begin(),v.end(), MyCompare ); printf("\n"); for(int i=0; i<10; i++) { printf("%d", v[i]); } printf("\n"); } 1)預定義函數對象基本概念:標准模板庫STL提前定義了很多預定義函數對象,#include //1使用預定義函數對象: //類模板plus<> 的實現了: 不同類型的數據進行加法運算 void main41() { plus intx = 10; inty = 20; intz = intAdd(x, y); //等價於 x + y cout<< z << endl; plus stringmyc = stringAdd("aaa", "bbb"); cout<< myc << endl; vector v1.push_back("bbb"); v1.push_back("aaa"); v1.push_back("ccc"); v1.push_back("zzzz"); //缺省情況下,sort()用底層元素類型的小於操作符以升序排列容器的元素。 //為了降序,可以傳遞預定義的類模板greater,它調用底層元素類型的大於操作符: cout<< "sort()函數排序" << endl;; sort(v1.begin(),v1.end(), greater for(vector { cout<< *it << endl; } } 2)算術函數對象 預定義的函數對象支持加、減、乘、除、求余和取反。調用的操作符是與type相關聯的實例 加法:plus plus sres = stringAdd(sva1,sva2); 減法:minus 乘法:multiplies 除法divides 求余:modulus 取反:negate negate ires = intNegate(ires); Ires= UnaryFunc(negate 3)關系函數對象 等於equal_to equal_to sres = stringEqual(sval1,sval2); 不等於not_equal_to 大於 greater 大於等於greater_equal 小於 less 小於等於less_equal void main42() { vector v1.push_back("bbb"); v1.push_back("aaa"); v1.push_back("ccc"); v1.push_back("zzzz"); v1.push_back("ccc"); strings1 = "ccc"; //intnum = count_if(v1.begin(),v1.end(), equal_to intnum = count_if(v1.begin(),v1.end(),bind2nd(equal_to cout<< num << endl; } 4)邏輯函數對象 邏輯與 logical_and logical_and ires = intAnd(ival1,ival2); dres=BinaryFunc( logical_and 邏輯或logical_or 邏輯非logical_not logical_not Ires = IntNot(ival1); Dres=UnaryFunc( logical_not 1)函數適配器的理論知識 2)常用函數函數適配器 標准庫提供一組函數適配器,用來特殊化或者擴展一元和二元函數對象。常用適配器是: 1綁定器(binder): binder通過把二元函數對象的一個實參綁定到一個特殊的值上,將其轉換成一元函數對象。C++標准庫提供兩種預定義的binder適配器:bind1st和bind2nd,前者把值綁定到二元函數對象的第一個實參上,後者綁定在第二個實參上。 2取反器(negator) :negator是一個將函數對象的值翻轉的函數適配器。標准庫提供兩個預定義的ngeator適配器:not1翻轉一元預定義函數對象的真值,而not2翻轉二元謂詞函數的真值。 常用函數適配器列表如下: bind1st(op, value) bind2nd(op, value) not1(op) not2(op) mem_fun_ref(op) mem_fun(op) ptr_fun(op) 3)常用函數適配器案例 ////////////////////////////////////////////////////////////////////////// class IsGreat { public: IsGreat(inti) { m_num= i; } booloperator()(int &num) { if(num > m_num) { returntrue; } returnfalse; } protected: private: intm_num; }; void main43() { vector for(int i=0; i<5; i++) { v1.push_back(i+1); } for(vector { cout<< *it << " " ; } intnum1 = count(v1.begin(), v1.end(), 3); cout<< "num1:" << num1 << endl; //通過謂詞求大於2的個數 intnum2 = count_if(v1.begin(), v1.end(), IsGreat(2)); cout<< "num2:" << num2 << endl; //通過預定義函數對象求大於2的個數 greater // param> 2 intnum3 = count_if(v1.begin(), v1.end(), bind2nd(greater cout<< "num3:" << num3 << endl; //取模 能被2整除的數 求奇數 intnum4 = count_if(v1.begin(), v1.end(), bind2nd(modulus cout<< "奇數num4:" << num4 << endl; intnum5 = count_if(v1.begin(), v1.end(), not1( bind2nd(modulus cout<< "偶數num5:" << num5 << endl; return; } 1) STL的容器通過類模板技術,實現數據類型和容器模型的分離 2) STL的迭代器技術實現了遍歷容器的統一方法;也為STL的算法提供了統一性奠定了基礎 3) STL的算法,通過函數對象實現了自定義數據類型的算法運算;所以說:STL的算法也提供了統一性。 核心思想:其實函數對象本質就是回調函數,回調函數的思想:就是任務的編寫者和任務的調用者有效解耦合。函數指針做函數參數。 4) 具體例子:transform算法的輸入,通過迭代器first和last指向的元算作為輸入;通過result作為輸出;通過函數對象來做自定義數據類型的運算。、 2 for_each: 用指定函數依次對指定范圍內所有元素進行迭代訪問。該函數不得修改序列中的元素。 2 函數定義。For_each(begin,end, func); template class _Fn1> inline _Fn1for_each(_InIt _First, _InIt _Last, _Fn1 _Func) { // perform functionfor each element _DEBUG_RANGE(_First,_Last); _DEBUG_POINTER(_Func); return (_For_each(_Unchecked(_First),_Unchecked(_Last), _Func)); } 2 注意for_each的第三個參數 函數對象做函數參數,函數對象做返回值 class CMyShow { public: CMyShow() { num= 0; } voidoperator()(const int &iItem) { num++; cout<< iItem; } voidprintCount() { cout<< "num:" << num << endl; } private: intnum; }; void show(const int &iItem) { cout<< iItem; } main() { intiArray[] = {0,1,2,3,4}; vector for_each(vecInt.begin(), vecInt.end(), show); //結果打印出0 1 2 3 4 CMyShow show1 = for_each(vecInt.begin(), vecInt.end(), CMyShow()); cout << endl; show1.printCount(); //顯示對象被調用的次數 } 2 transform: 與for_each類似,遍歷所有元素,但可對容器的元素進行修改 2 transform()算法有兩種形式: 2 transform(b1, e1, b2, op) 2 transform(b1, e1, b2, b3, op) template class _OutIt, class _Fn1> inline _OutIt transform(_InIt _First, _InIt _Last,_OutIt _Dest, _Fn1 _Func) 2 transform()的作用 例如:可以一個容器的元素,通過op,變換到另一個容器中(同一個容器中) 也可以把兩個容器的元素,通過op,變換到另一個容器中 2 注意: 1.如果目標與源相同,transform()就和for_each()一樣。 2.如果想以某值替換符合規則的元素,應使用replace()算法 int increase (int i) { return i+1; } main() { vector vecIntA.push_back(1); vecIntA.push_back(3); vecIntA.push_back(5); vecIntA.push_back(7); vecIntA.push_back(9); transform(vecIntA.begin(),vecIntA.end(),vecIntA.begin(),increase); //vecIntA: {2,4,6,8,10} transform(vecIntA.begin(),vecIntA.end(),vecIntA.begin(),negate } 1)STL 算法 – 修改性算法 v for_each() v copy() v copy_backward() v transform() v merge() v swap_ranges() v fill() v fill_n() v generate() v generate_n() v replace v replace_if() v replace_copy() v replace_copy_if() 2) v for_each() 速度快 不靈活 v transform() 速度慢 非常靈活 //一般情況下:for_each所使用的函數對象,參數是引用,沒有返回值 void mysquare(int &num) { num= num * num; } //transform所使用的函數對象,參數一般不使用引用,而是還有返回值 int mysquare2(int num) //結果的傳出,必須是通過返回值 { returnnum = num * num; } void main_foreach_pk_tranform() { vector v1.push_back(1); v1.push_back(3); v1.push_back(5); vector for_each(v1.begin(),v1.end(), mysquare); printAA(v1); cout<< endl; transform(v2.begin(),v2.end(), v2.begin(), mysquare2); printAA(v2); cout<< endl; } 在iterator對標識元素范圍內,查找一對相鄰重復元素,找到則返回指向這對元素的第一個元素的迭代器。否則返回past-the-end。 vector vecInt.push_back(1); vecInt.push_back(2); vecInt.push_back(2); vecInt.push_back(4); vecInt.push_back(5); vecInt.push_back(5); vector 在有序序列中查找value,找到則返回true。注意:在無序序列中,不可使用。 set setInt.insert(3); setInt.insert(1); setInt.insert(7); setInt.insert(5); setInt.insert(9); boolbFind = binary_search(setInt.begin(),setInt.end(),5); 利用等於操作符,把標志范圍內的元素與輸入值比較,返回相等的個數。 vector vecInt.push_back(1); vecInt.push_back(2); vecInt.push_back(2); vecInt.push_back(4); vecInt.push_back(2); vecInt.push_back(5); intiCount = count(vecInt.begin(),vecInt.end(),2); //iCount==3 假設vector //先定義比較函數 bool GreaterThree(int iNum) { if(iNum>=3) { returntrue; } else { returnfalse; } } int iCount = count_if(vecIntA.begin(),vecIntA.end(), GreaterThree); //此時iCount == 4 2 find: 利用底層元素的等於操作符,對指定范圍內的元素與輸入值進行比較。當匹配時,結束搜索,返回該元素的迭代器。 2 equal_range: 返回一對iterator,第一個表示lower_bound,第二個表示upper_bound。 vector vecInt.push_back(1); vecInt.push_back(3); vecInt.push_back(5); vecInt.push_back(7); vecInt.push_back(9); vector find_if:使用輸入的函數代替等於操作符執行find。返回被找到的元素的迭代器。 假設vector vector 此時 *it==3, *(it+1)==5, *(it+2)==3, *(it+3)==9 2 以下是排序和通用算法:提供元素排序策略 2 merge: 合並兩個有序序列,存放到另一個序列。 例如:vecIntA,vecIntB,vecIntC是用vector vecIntC.resize(9); //擴大容量 merge(vecIntA.begin(),vecIntA.end(),vecIntB.begin(),vecIntB.end(),vecIntC.begin()); 此時vecIntC就存放了按順序的1,2,3,4,5,6,7,8,9九個元素 2 sort: 以默認升序的方式重新排列指定范圍內的元素。若要改排序規則,可以輸入比較函數。 //學生類 Class CStudent: { public: CStudent(int iID, string strName) { m_iID=iID; m_strName=strName; } public: intm_iID; stringm_strName; } //學號比較函數 bool Compare(const CStudent &stuA,constCStudent &stuB) { return (stuA.m_iID } void main() { vector vecStu.push_back(CStudent(2,"老二")); vecStu.push_back(CStudent(1,"老大")); vecStu.push_back(CStudent(3,"老三")); vecStu.push_back(CStudent(4,"老四")); sort(vecStu.begin(),vecStu.end(),Compare); // 此時,vecStu容器包含了按順序的"老大對象","老二對象","老三對象","老四對象" } 2 random_shuffle: 對指定范圍內的元素隨機調整次序。 srand(time(0)); //設置隨機種子 vector vecInt.push_back(1); vecInt.push_back(3); vecInt.push_back(5); vecInt.push_back(7); vecInt.push_back(9); stringstr("itcastitcast "); random_shuffle(vecInt.begin(),vecInt.end()); //隨機排序,結果比如:9,7,1,5,3 random_shuffle(str.begin(),str.end()); //隨機排序,結果比如:" itstcasticat" vector vecInt.push_back(1); vecInt.push_back(3); vecInt.push_back(5); vecInt.push_back(7); vecInt.push_back(9); reverse(vecInt.begin(),vecInt.end()); //{9,7,5,3,1} vector vecIntA.push_back(1); vecIntA.push_back(3); vecIntA.push_back(5); vecIntA.push_back(7); vecIntA.push_back(9); vector vecIntB.resize(5); //擴大空間 copy(vecIntA.begin(),vecIntA.end(), vecIntB.begin()); //vecIntB:{1,3,5,7,9} 2 replace(beg,end,oldValue,newValue): 將指定范圍內的所有等於oldValue的元素替換成newValue。 vector vecIntA.push_back(1); vecIntA.push_back(3); vecIntA.push_back(5); vecIntA.push_back(3); vecIntA.push_back(9); replace(vecIntA.begin(),vecIntA.end(), 3, 8); //{1,8,5,8,9} 2 replace_if : 將指定范圍內所有操作結果為true的元素用新值替換。 用法舉例: replace_if(vecIntA.begin(),vecIntA.end(),GreaterThree,newVal) 其中vecIntA是用vector GreaterThree 函數的原型是 boolGreaterThree(int iNum) //把大於等於3的元素替換成8 vector vecIntA.push_back(1); vecIntA.push_back(3); vecIntA.push_back(5); vecIntA.push_back(3); vecIntA.push_back(9); replace_if(vecIntA.begin(),vecIntA.end(), GreaterThree, 8); //GreaterThree的定義在上面。 2 swap: 交換兩個容器的元素 vector vecIntA.push_back(1); vecIntA.push_back(3); vecIntA.push_back(5); vector vecIntB.push_back(2); vecIntB.push_back(4); swap(vecIntA,vecIntB); //交換 2 accumulate: 對指定范圍內的元素求和,然後結果再加上一個由val指定的初始值。 2#include vector vecIntA.push_back(1); vecIntA.push_back(3); vecIntA.push_back(5); vecIntA.push_back(7); vecIntA.push_back(9); intiSum = accumulate(vecIntA.begin(), vecIntA.end(), 100); //iSum==125 2 fill: 將輸入值賦給標志范圍內的所有元素。 vector vecIntA.push_back(1); vecIntA.push_back(3); vecIntA.push_back(5); vecIntA.push_back(7); vecIntA.push_back(9); fill(vecIntA.begin(),vecIntA.end(), 8); //8, 8,8, 8, 8 2 set_union: 構造一個有序序列,包含兩個有序序列的並集。 2 set_intersection: 構造一個有序序列,包含兩個有序序列的交集。 2 set_difference: 構造一個有序序列,該序列保留第一個有序序列中存在而第二個有序序列中不存在的元素。 vector vecIntA.push_back(1); vecIntA.push_back(3); vecIntA.push_back(5); vecIntA.push_back(7); vecIntA.push_back(9); vector vecIntB.push_back(1); vecIntB.push_back(3); vecIntB.push_back(5); vecIntB.push_back(6); vecIntB.push_back(8); vector vecIntC.resize(10); //並集 set_union(vecIntA.begin(),vecIntA.end(), vecIntB.begin(), vecIntB.end(), vecIntC.begin()); //vecIntC :{1,3,5,6,7,8,9,0,0,0} //交集 fill(vecIntC.begin(),vecIntC.end(),0); set_intersection(vecIntA.begin(),vecIntA.end(), vecIntB.begin(), vecIntB.end(), vecIntC.begin()); //vecIntC: {1,3,5,0,0,0,0,0,0,0} //差集 fill(vecIntC.begin(),vecIntC.end(),0); set_difference(vecIntA.begin(),vecIntA.end(), vecIntB.begin(), vecIntB.end(), vecIntC.begin()); //vecIntC:{7,9,0,0,0,0,0,0,0,0} 1)某市舉行一場演講比賽( speech_contest),共有24個人參加。比賽共三輪,前兩輪為淘汰賽,第三輪為決賽。 2)比賽方式:分組比賽,每組6個人;選手每次要隨機分組,進行比賽; 第一輪分為4個小組,每組6個人。比如100-105為一組,106-111為第二組,依次類推, 每人分別按照抽簽(draw)順序演講。當小組演講完後,淘汰組內排名最後的三個選手,然後繼續下一個小組的比賽。 第二輪分為2個小組,每組6人。比賽完畢,淘汰組內排名最後的三個選手,然後繼續下一個小組的比賽。 第三輪只剩下6個人,本輪為決賽,選出前三名。 4)比賽評分:10個評委打分,去除最低、最高分,求平均分 每個選手演講完由10個評委分別打分。該選手的最終得分是去掉一個最高分和一個最低分,求得剩下的8個成績的平均分。 選手的名次按得分降序排列,若得分一樣,按參賽號升序排名。 用STL編程,求解這個問題 1) 請打印出所有選手的名字與參賽號,並以參賽號的升序排列。 2) 打印每一輪比賽後,小組比賽成績和小組晉級名單 3) 打印決賽前三名,選手名稱、成績。 //產生選手 ( ABCDEFGHIJKLMNOPQRSTUVWXYZ) 姓名、得分;選手編號 //第1輪 選手抽簽選手比賽 查看比賽結果 //第2輪 選手抽簽選手比賽 查看比賽結果 //第3輪 選手抽簽選手比賽 查看比賽結果 需要把選手信息、選手得分信息、選手比賽抽簽信息、選手的晉級信息保存在容器中,需要涉及到各個容器的選型。(相當於信息的數據庫E-R圖設計) 選手可以設計一個類Speaker(姓名和得分) 所有選手編號和選手信息,可以放在容器內:map 所有選手的編號信息,可以放在容器:vecter 第1輪晉級名單,可以放在容器vecter 第2輪晉級名單,可以放在容器vecter 第3輪前三名名單,可以放在容器vecter 每個小組的比賽得分信息,按照從小到大的順序放在 multimap<成績, 編號, greater 也就是:multimap 每個選手的得分,可以放在容器deque 1) 搭建框架 2) 完善業務函數 random_shuffle 3) 測試 void main() { //定義數據結構 所有選手放到容器中 map vector vector vector vector //產生選手 GenSpeaker(mapSpeaker,v1); //第1輪 選手抽簽選手比賽 查看比賽結果(晉級名單 得分情況) cout<< "\n\n\n任意鍵,開始第一輪比賽" << endl; cin.get(); speech_contest_draw(v1); speech_contest(0, v1, mapSpeaker, v2); speech_contest_print(0,v2, mapSpeaker); //第2輪 選手抽簽 選手比賽 查看比賽結果 cout<< "\n\n\n任意鍵,開始第二輪比賽" << endl; cin.get(); speech_contest_draw(v2); speech_contest(1, v2, mapSpeaker, v3); speech_contest_print(1,v3, mapSpeaker); //第3輪 選手抽簽 選手比賽 查看比賽結果 cout<< "\n\n\n任意鍵,開始第三輪比賽" << endl; cin.get(); speech_contest_draw(v3); speech_contest(2, v3, mapSpeaker, v4); speech_contest_print(2,v4, mapSpeaker); system("pause"); } //產生選手 int GenSpeaker(map //選手抽簽 int speech_contest_draw(vector //選手比賽 int speech_contest(int index, vector //打印選手比賽晉級名單 int speech_contest_print(int index,vector 作業: 市區中學,足球比賽
input a:34↙(輸入a的值)
dec:34 (十進制形式)
hex:22 (十六進制形式)
oct:42 (八進制形式)
China (域寬為)
*****China (域寬為,空白處以'*'填充)
pi=3.14285714e+00 (指數形式輸出,8位小數)
pi=3.1429e+00 (指數形式輸出,4位小數)
pi=3.143 (小數形式輸出,精度仍為)
double a=123.456789012345; // 對a賦初值
1) cout< 2) cout<
5) cout<
int b=123456; // 對b賦初值
1) cout< 2) cout<
如果在多個cout語句中使用相同的setw(n),並使用setiosflags(ios::right),可以實現各行數據右對齊,如果指定相同的精度,可以實現上下小數點對齊。
123.46 (字段寬度為10,右對齊,取兩位小數)
3.14
-3214.67
先統一設置定點形式輸出、取兩位小數、右對齊。這些設置對其後的輸出均有效(除非重新設置),而setw只對其後一個輸出項有效,因此必須在輸出a,b,c之前都要寫setw(10)。
dec:21(十進制形式)
hex:0x15 (十六進制形式,以x開頭)
oct:025 (八進制形式,以開頭)
China (域寬為)
*****China (域寬為,空白處以'*'填充)
pi=**3.142857e+00 (指數形式輸出,域寬,默認位小數)
+***3.142857 (小數形式輸出,精度為,最左側輸出數符“+”)
cout <<20 <<3.14<9.3文件I/O
9.3.1文件流類和文件流對象
9.3.2C++文件的打開與關閉
打開文件
1) 新版本的I/O類庫中不提供ios::nocreate和ios::noreplace。
2) 每一個打開的文件都有一個文件指針,該指針的初始位置由I/O方式指定,每次讀寫都從文件指針的當前位置開始。每讀入一個字節,指針就後移一個字節。當文件指針移到最後,就會遇到文件結束EOF(文件結束符也占一個字節,其值為-1),此時流對象的成員函數eof的值為非0值(一般設為1),表示文件結束了。
3) 可以用“位或”運算符“|”對輸入輸出方式進行組合,如表13.6中最後3行所示那樣。還可以舉出下面一些例子:
ios::in | ios:: noreplace //打開一個輸入文件,若文件不存在則返回打開失敗的信息
ios::app | ios::nocreate //打開一個輸出文件,在文件尾接著寫數據,若文件不存在,則返回打開失敗的信息
ios::out l ios::noreplace //打開一個新文件作為輸出文件,如果文件已存在則返回打開失敗的信息
ios::in l ios::out I ios::binary //打開一個二進制文件,可讀可寫
但不能組合互相排斥的方式,如 ios::nocreate l ios::noreplace。
4) 如果打開操作失敗,open函數的返回值為0(假),如果是用調用構造函數的方式打開文件的,則流對象的值為0。可以據此測試打開是否成功。如
if(outfile.open("f1.bat", ios::app) ==0)
cout <<"open error";
或
if( !outfile.open("f1.bat", ios::app) )
cout <<"open error";關閉文件
outfile.close( ); //將輸出文件流所關聯的磁盤文件關閉
所謂關閉,實際上是解除該磁盤文件與文件流的關聯,原來設置的工作方式也失效,這樣,就不能再通過文件流對該文件進行輸入或輸出。此時可以將文件流與其他磁盤文件建立關聯,通過文件流對新的文件進行輸入或輸出。如
outfile.open("f2.dat",ios::app|ios::nocreate);
此時文件流outfile與f2.dat建立關聯,並指定了f2.dat的工作方式。9.3.3C++對ASCII文件的讀寫操作
案例1:寫文件,然後讀文件
案例2(自學擴展思路)
usingnamespace std;
int main()
{
ofstream myfile("c:\\1.txt",ios::out|ios::trunc,0);
myfile<<"傳智播客"<
system("pause");
}
#include
usingnamespace std;
int main()
{
ofstream myfile("c:\\1.txt",ios::app,0);
if(!myfile)//或者寫成myfile.fail()
{
cout<<"文件打開失敗,目標文件狀態可能為只讀!";
system("pause");
exit(1);
}
myfile<<"傳智播客"<
#include
usingnamespace std;
int main()
{
ofstream myfile;
myfile.open("c:\\1.txt",ios::out|ios::app,0);
if(!myfile)//或者寫成myfile.fail()
{
cout<<"文件創建失敗,磁盤不可寫或者文件為只讀!";
system("pause");
exit(1);
}
myfile<<"傳智播客"<
}
#include
#include
usingnamespace std;
int main()
{
ifstream myfile;
myfile.open("c:\\1.txt",ios::in,0);
if(!myfile)
{
cout<<"文件讀錯誤";
system("pause");
exit(1);
}
char ch;
string content;
while(myfile.get(ch))
{
content+=ch;
cout.put(ch);//cout<
myfile.close();
cout<
}
#include
usingnamespace std;
int main()
{
fstream myfile;
myfile.open("c:\\1.txt",ios::out|ios::app,0);
if(!myfile)
{
cout<<"文件寫錯誤,文件屬性可能為只讀!"<
exit(1);
}
myfile<<"傳智播客"<
myfile.open("c:\\1.txt",ios::in,0);
if(!myfile)
{
cout<<"文件讀錯誤,文件可能丟失!"<
exit(1);
}
char ch;
while(myfile.get(ch))
{
cout.put(ch);
}
myfile.close();
system("pause");
}9.3.4 C++對二進制文件的讀寫操作
用成員函數read和write讀寫二進制文件
istream& read(char *buffer,int len);
ostream& write(const char * buffer,int len);
字符指針buffer指向內存中一段存儲空間。len是讀寫的字節數。調用的方式為:
a. write(p1,50);
b. read(p2,30);
上面第一行中的a是輸出文件流對象,write函數將字符指針p1所給出的地址開始的50個字節的內容不加轉換地寫到磁盤文件中。在第二行中,b是輸入文件流對象,read 函數從b所關聯的磁盤文件中,讀入30個字節(或遇EOF結束),存放在字符指針p2所指的一段空間內。案例1
9.4作業練習
10、STL實用技術專題
10.1 STL(標准模板庫)理論基礎
10.1.1基本概念
、
10.1.2容器
,
10.1.2.1容器的概念
10.1.2.2容器的分類
10.1.3迭代器
10.1.4算法
10.1.5C++標准庫
10.1.6模板簡要回顧
10.2容器
10.2.1 STL的string
1String概念
2string的構造函數
3string的存取字符操作
4從string取得const char*的操作
5把string拷貝到char*指向的內存空間的操作
6string的長度
7string的賦值
8string字符串連接
9string的比較
10string的子串
11string的查找和 替換
12String的區間刪除和插入
13string算法相關
10.2.2Vector容器
1Vector容器簡介
2vector對象的默認構造
3vector對象的帶參數構造
4vector的賦值
5vector的大小
6vector末尾的添加移除操作
7vector的數據存取
8迭代器基本原理
9雙向迭代器與隨機訪問迭代器
10vector與迭代器的配合使用
11vector的插入
12vector的刪除
13vector小結
10.2.3Deque容器
Deque簡介
deque對象的默認構造
deque末尾的添加移除操作
deque的數據存取
deque與迭代器
deque對象的帶參數構造
deque的賦值
deque的大小
deque的插入
deque的刪除
10.2.4stack容器
stack對象的默認構造
stack的push()與pop()方法
stack對象的拷貝構造與賦值
stack的數據存取
stack的大小
10.2.5Queue容器
Queue簡介
queue對象的默認構造
queue的push()與pop()方法
queue對象的拷貝構造與賦值
queue的數據存取
queue的大小
10.2.6List容器
List簡介
list對象的默認構造
list頭尾的添加移除操作
list的數據存取
list與迭代器
list對象的帶參數構造
list的賦值
list的大小
list的插入
list的刪除
list的反序排列
小結:
10.2.7優先級隊列priority_queue
10.2.8Set和multiset容器
set/multiset的簡介
set/multiset對象的默認構造
set的插入與迭代器
Set集合的元素排序
函數對象functor的用法
set對象的拷貝構造與賦值
set的大小
set的刪除
set的查找
pair的使用
小結
10.2.9Map和multimap容器
map/multimap的簡介
map對象的拷貝構造與賦值
map的大小
map的刪除
map的查找
10.2.10容器共性機制研究
10.2.9.1容器的共通能力
10.2.9.2各個容器的使用時機
10.2.11其他
10.3算法
10.3.1算法基礎
10.3.1.1算法概述
10.3.1.2 STL中算法分類
刪除算法 remove、remove_if、remove_copy、… 修改算法 for_each、transform 排序算法 sort、stable_sort、partial_sort、 排序算法 包括對序列進行排序和合並的算法、搜索算法以及有序序列上的集合操作 數值算法 對容器內容進行數值計算
10.3.1.3查找算法(13個):判斷容器中是否包含某個值
10.3.1.4堆算法(4個)
10.3.1.5關系算法(8個)
10.3.1.6集合算法(4個)
10.3.1.6列組合算法(2個)
10.3.1.7排序和通用算法(14個):提供元素排序策略
10.3.1.8刪除和替換算法(15個)
10.3.1.9生成和變異算法(6個)
10.3.1.10算數算法(4個)
10.3.1.11常用算法匯總
10.3.2STL算法中函數對象和謂詞
10.3.2.1函數對象和謂詞定義
10.3.2.2一元函數對象案例
10.3.2.3一元謂詞案例
10.3.2.4二元函數對象案例
10.3.2.5二元謂詞案例
10.3.2.6預定義函數對象和函數適配器
10.3.2.7函數適配器
10.3.2.8 STL的容器算法迭代器的設計理念
10.3.3常用的遍歷算法
for_each()
transform()
for_each()和transform()算法比較
10.3.4常用的查找算法
adjacent_find()
binary_search
count()
count_if()
find()
find_if()
10.3.5常用的排序算法
merge()
sort()
random_shuffle()
reverse()
10.3.6常用的拷貝和替換算法
copy()
replace()
replace_if()
swap()
10.3.7常用的算術和生成算法
accumulate()
fill()
10.3.8常用的集合算法
set_union(),set_intersection(),set_difference()
10.4 STL綜合案例
10.4.1案例學校演講比賽
10.4.1.1學校演講比賽介紹
10.4.1.2需求分析
10.4.1.3實現思路
10.4.1.4實現細節
10.4.2案例:足球比賽