筆記呢只簡要記錄了老師講課的關鍵知識點和結構,然后積累了一些有用的代碼小片段和解釋性很強的例子,之所以沒有分章節是為了使用瀏覽器頁內查找(ctrl+f/ command+f)時方便。
課程的編程作業的代碼我也上傳了gayhub(在這里呀https://github.com/BMR731/XueTangCplusplus).
有錯誤的地方希望大家指出啦,有一起繼續學習C++的小伙伴也可以一起交流哦~
- auto 變量類型,如
auto i = j+k;
vector<int> v(5);
for(auto e : v){
cout<<e;
}
- 控制臺傳參數
int main(int argc, char* argv[]){
//argc是參數的個數,包括程序執行本身的一個參數;argv是一個字符串的數組
}
- decltype 的使用,
decltype(i) j = 2;//聲明一個與i同類型的j
- C++風格的安全類型轉換,如
int j = static_cast<int>(i);
- dynamic_cast的轉換,dynamic_cast主要用于類層次間的上行轉換和下行轉換,還可以用于類之間的交叉轉換。在類層次間進行上行轉換時,dynamic_cast和[static_cast]的效果是一樣的;
在進行下行轉換時,dynamic_cast具有類型檢查的功能,比static_cast更安全。
下面是類型轉換時有用的代碼片段:
if(Derived* dp= dynamic_cast<Derived*)(bp)){
//轉換成功,dp指向Derived對象
}else{
//轉換失敗,bp指向Base對象,
}
//把轉換語句寫在條件判斷中更加安全
- char to int 的簡單轉換
int char2int(char c){
return static_cast<int>(c) - 48;
}
- 保留小數點后兩位
float f = 1.234;
cout<<fixed<<setprecision(2)<<f<<endl;
- 反轉一個正整數的代碼段
unsigned i = n;//n is the number we try to reverse
int m=0;//m is the auxiliary number
while(i>0){
m= m*10 + i%10;
i = i/10;
}
//m is the reversed n;
- 隨機數的使用
#include <cstdlib>
cin>>seed;
srand(seed);
int i = rand()%6 + 1;//模擬扔骰子
- 【todo】可變參數傳遞問題
- 內聯函數: 聲明時使用關鍵字 inline。編譯時在調用處用函數體進行替換,節省了參數傳遞、控制轉移等開銷。
注意:
內聯函數體內不能有循環語句和switch語句;
內聯函數的定義必須出現在內聯函數第一次被調用之前;
對內聯函數不能進行異常接口聲明。
inline double calArea(int r){//計算圓面積
return PI*r*r;
}
- 傳遞引用有兩個優點:
- 為了實現雙向修改
- 為了節省傳遞的開銷,但又不允許修改源數據,如復制構造函數
foo(const class &A){...}
,添加const關鍵詞即可
- constexpr函數:constexpr修飾的函數在其所有參數都是constexpr時,一定返回constexpr;并且只有一條return語句;好處是表達式的值可以在編譯時進行確定。
constexpr int get_size(){ return 20;}
constexpr int foo = get_size();//此時可以確保foo是一個常量表達式
- 默認參數值:
int add(int x, int y=2, int z=3);//對的,默認參數給的順序必須從右到左
int add(int x =1, int y, int z=3);//錯
- 函數重載:注意一下,返回值和形參類型不能區分重載函數即可
- 在自定義構造函數后仍希望編譯器給出默認參數的話,可這樣寫
clock() = default;
- 用初始化列表的方式賦值更快
Clock::Clock(int newH,int newM,int newS): hour(newH),minute(newM), second(newS) {
}
- 設計類時,寫一個的默認構造函數是良好的設計規范。
- 委托構造函數就是可以在構造函數中調用其他構造函數的機制
- 復制構造函數
class 類名 {
public :
類名(形參);//構造函數
類名(const 類名 &對象名);//復制構造函數
// ...
};
類名::類( const 類名 &對象名)//復制構造函數的實現
{ 函數體 }
//如果不希望被復制
//class Point { //Point 類的定義
public:
Point(int xx=0, int yy=0) { x = xx; y = yy; } //構造函數,內聯
Point(const Point& p) =delete; //指示編譯器不生成默認復制構造函數
private:
int x, y; //私有數據
};
//復制構造函數被調用的三種時機
//1 用一個對象初始化對象時
//2 形參和實參結合時
//3 return 語句返回一個無名對象時
- 析構函數
完成對象被刪除前的一些清理工作。
在對象的生存期結束的時刻系統自動調用它,然后再釋放此對象所屬的空間。
如果程序中未聲明析構函數,編譯器將自動產生一個默認的析構函數,其函數體為空。
class Point {
public:
Point(int xx,int yy);
~Point();
//...其他函數原型
private:
int x, y;
};
- 前向引用申明:為了解決兩個類在定義時相互引用的情況,但又不能完美解決所有情況,如它可以解決充當形參的情況,但不能解決充當成員變量的情況,因為涉及到具體的字節數等細節問題。
class B; //前向引用聲明
class A {
public:
void f(B b);
};
class B {
public:
void g(A a);
};
- 類的靜態成員別忘了在類外進行定義和初始化
class foo{
private:
static int count;
}
int foo::count =0;//this line don't forget;在類外進行定義和初始化!
int main(){}
- 結構體;結構體是一種特殊形態的類
與類的唯一區別:類的缺省訪問權限是private,結構體的缺省訪問權限是public
結構體存在的主要原因:與C語言保持兼容
什么時候用結構體而不用類呢?定義主要用來保存數據、而沒有什么操作的類型;人們習慣將結構體的數據成員設為公有,因此這時用結構體更方便 - 聯合體:目的是存儲空間的共用,減少冗余和錯誤。
- 枚舉類:實質上就是強類型的枚舉,與簡單枚舉相比,防止沖突;類型要求嚴格;更加多樣的基本類型,
enum class Type: char { General, Light, Medium, Heavy};
- 類的友元:友元機制是破壞封裝的一種機制,為的是提供封裝和效率的折中,在水平不高時最好少使用;友元是一種單向的關系;
//友元函數
class Point { //Point類聲明
public: //外部接口
Point(int x=0, int y=0) : x(x), y(y) { }
int getX() { return x; }
int getY() { return y; }
friend float dist(Point &a, Point &b);
private: //私有數據成員
int x, y;
};
float dist( Point& a, Point& b) {
double x = a.x - b.x;
double y = a.y - b.y;
return static_cast<float>(sqrt(x * x + y * y));
}
//友元類
class A {
friend class B;
public:
void display() {
cout << x << endl;
}
private:
int x;
};
class B {
public:
void set(int i);
void display();
private:
A a;
};
void B::set(int i) {
a.x=i;
}
void B::display() {
a.display();
};
- const的用法
- 常函數
void A::print() const;
對于保證不改變對象狀態的函數,優先聲明const來得到編譯器的保證檢查。 - 常變量
const int a = 2;
- 常引用:用于傳引用但保證單向傳遞的情況,
void foo(const int &a)
- 常函數
- 多文件結構
- .h文件:類的聲明
- .cpp文件: 類的實現
- main()所在文件:類的使用文件
- 預編譯指令:
#include
#define
#if....#endif 條件編譯
#if..#elif....#else...#endif
#ifdef.. #endif 如果標記被定義過
#ifndef....#endif 如果標記未被定義過
最常用的用法在類的聲明文件中,為了避免重復編譯,常這樣寫:
#ifndef CLIENT_H
#define CLIENT_H
...類的聲明
#endif
- 指針相關
const int* p = &i; //表明p為只讀指針,但指針本身可以指向其他地方
int* const p = &i;//表明指針本身只能指向i的地址,但可對i進行讀寫操作
//void指針作為通用指針來使用
void* p;
int i=0;
p = &i;
int* p2 = static_cast<int*>(p);
//空指針;
int* p = nullptr; //c++11 推薦
p==0;//判斷指針是否為空
指針做函數參數,為什么要用指針
需要數據雙向傳遞時(引用也可以達到此效果)
需要傳遞一組數據,只傳首地址運行效率比較高返回指針類型的函數,1.特別注意返回的地址不能是局部變量的地址,必須在主調函數中有效。2.函數返回用new分配的空間分配的地址是可以的,但主調函數必須記得釋放空間
函數指針。函數指針的主要用途是實現函數回調,從而調用者可以將函數作為參數,更加靈活的處理數據。函數指針與其他類型的指針聲明相同,只不過要求更多一些,需要表明函數的返回值,參數表,如
int(*func)(int, int)
表明這是一個指向返回值類型為int,參數表為(int,int)的函數的程序代碼的地址。
int compute(int a, int b, int(*func)(int,int)){
return func(a,b);
}
int max(int a, int b){return ((a>b)? a:b;)}
int min(int a, int b){return ((a<b)? a:b;)}
res = compute(a,b,&max);
res = compute(a,b,&min);
- 對象指針,1. 了解
pa->getX()與(*pa).getX()
等價 2.了解this指針 - 動態分配內存
//分配多維數組
int (*cp)[8][9] = new int[7][8][9];
左值和右值的問題,實質上是能不能修改的問題,右值表示只可以讀但不可以修改,而左值才能修改,返回左值的常見做法是返回引用類型或指針類型。
智能指針[Todo有待補充理解]
unique_ptr :不允許多個指針共享資源,可以用標準庫中的move函數轉移指針
shared_ptr :多個指針共享資源
weak_ptr :可復制shared_ptr,但其構造或者釋放對資源不產生影響深層復制和淺層復制
當類成員為指針變量時,如一個數組,淺層復制只是復制了指針的值,而深層復制才可以復制指針所指的內容,此時多需要重寫復制拷貝函數移動構造函數:在一些情況下,不需要復制構造時,而只需簡單移動,將控制權轉移給目標對象時,可使用移動構造函數,書寫格式為
class_name ( class_name && )
&&表示右值引用。求字符串所有子序列
vector<string> get_subsequences(const string str){
long len = str.length();
long num = 1<<str.length();//將1左移len位,求2的len次冪。
vector<string> res;
for (int i = 1; i <num ; ++i) {
string ss;
for (int j = 0; j < len; ++j) {
if(i&(1<<j)) ss.push_back(str[j]);
}
res.push_back(ss);
}
return res;
}
- vector的使用
vector<int> nums(5,2);//初始化
sort(nums.begin(), nums.end());//排序
派生的繼承方式:
公有繼承(public)
繼承的訪問控制:
基類的public和protected成員:訪問屬性在派生類中保持不變;
基類的private成員:不可直接訪問。
訪問權限:
派生類中的成員函數:可以直接訪問基類中的public和protected成員,但不能直接訪問基類的private成員;
通過派生類的對象:只能訪問public成員。
私有繼承
繼承的訪問控制
基類的public和protected成員:都以private身份出現在派生類中;
基類的private成員:不可直接訪問。
訪問權限
派生類中的成員函數:可以直接訪問基類中的public和protected成員,但不能直接訪問基類的private成員;
通過派生類的對象:不能直接訪問從基類繼承的任何成員。
保護繼承(protected)
繼承的訪問控制
基類的public和protected成員:都以protected身份出現在派生類中;
基類的private成員:不可直接訪問。
訪問權限
派生類中的成員函數:可以直接訪問基類中的public和protected成員,但不能直接訪問基類的private成員;
通過派生類的對象:不能直接訪問從基類繼承的任何成員。
protected 成員的特點與作用
對建立其所在類對象的模塊來說,它與 private 成員的性質相同。
對于其派生類來說,它與 public 成員的性質相同。
既實現了數據隱藏,又方便繼承,實現代碼重用。
如果派生類有多個基類,也就是多繼承時,可以用不同的方式繼承每個基類。基類和私有類之間的類型轉換
公有派生類對象可以被當作基類的對象使用,反之則不可。
派生類的對象可以隱含轉換為基類對象;
派生類的對象可以初始化基類的引用;
派生類的指針可以隱含轉換為基類的指針。
通過基類對象名、指針只能使用從基類繼承的成員。派生類的構造函數:首先,按照繼承的次序,確保給基類帶參數的初始化函數送去參數,接著按照類成員的聲明次序初始化類成員,最后調用構造函數。
class C: public B {
public:
C();
C(int i, int j);
~C();
void print() const;
private:
int c;
};
C::C(int i,int j): B(i), c(j){
cout << "C's constructor called." << endl;
}
派生類的復制構造函數
一般都要為基類的復制構造函數傳遞參數。
復制構造函數只能接受一個參數,既用來初始化派生類定義的成員,也將被傳遞給基類的復制構造函數。
基類的復制構造函數形參類型是基類對象的引用,實參可以是派生類對象的引用
例如:C::C(const C &c1): B(c1) {…}
派生類的析構函數:無需顯示調用,析構次序與初始化次序相反。
二義性:二義性可以發生在父子之間,父親之間,簡單的解決方案是使用類名加以限定即可,然而在多繼承下的二義性問題比較復雜,因此引入了虛基類,當多個父親有一個共同的祖先時,祖先里的成員存在不一致性和冗余的風險,因此通過虛繼承來保證祖先中的值只有一份,并且祖先的構造函數需要在每一代的構造函數中傳參,但實質上執行的只有最遠派生類調用的構造函數。
C++中class聲明的默認權限是private,struct聲明的默認權限是public
重載運算符,重載函數可以重載為類內成員函數,或者類外函數。
重載為類內成員函數的要求是,操作符的第一個參數必須是該類的類型。
//雙目運算符的重載
//例8-1復數類加減法運算重載為成員函數
Complex Complex::operator + (const Complex &c2) const{
//創建一個臨時無名對象作為返回值
return Complex(real+c2.real, imag+c2.imag);
}
//單目運算符的重載
//重載前置++
Clock & Clock::operator ++ () {
second++;
if (second >= 60) {
second -= 60; minute++;
if (minute >= 60) {
minute -= 60; hour = (hour + 1) % 24;
}
}
return *this;//返回值的本身引用,可以當左值被修改
}
//重載后置++
Clock Clock::operator ++ (int) {
//注意形參表中的整型參數
Clock old = *this;
++(*this); //調用前置“++”運算符
return old;//返回值的一個副本,只能做右值,不能做觸及到本身的修改
}
重載為非成員函數的規則
函數的形參代表依自左至右次序排列的各操作數。
重載為非成員函數時,參數個數=原操作數個數(后置++、--除外)
至少應該有一個自定義類型的參數。
后置單目運算符 ++和--的重載函數,形參列表中要增加一個int,但不必寫形參名。
如果在運算符的重載函數中需要操作某類對象的私有成員,可以將此函數聲明為該類的友元。
典型例題:
class Complex {
public:
Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) { }
friend Complex operator+(const Complex &c1, const Complex &c2);//聲明類外的函數為友元來提高訪問的效率
friend Complex operator-(const Complex &c1, const Complex &c2);
friend ostream & operator<<(ostream &out, const Complex &c);
private:
double real; //復數實部
double imag; //復數虛部
};
Complex operator+(const Complex &c1, const Complex &c2){//注意這里傳const的引用來提高傳輸效率
return Complex(c1.real+c2.real, c1.imag+c2.imag);
}
Complex operator-(const Complex &c1, const Complex &c2){
return Complex(c1.real-c2.real, c1.imag-c2.imag);
}
ostream & operator<<(ostream &out, const Complex &c){
out << "(" << c.real << ", " << c.imag << ")";
return out;//返回為ostream的引用來繼續保持cout的級聯輸出
}
-
虛函數:
- 虛函數是實現動態綁定的一種機制,告訴編譯器運行時綁定從而實現多態
- 虛函數同時不可以成為內聯函數,必須在類外定義
- 基類只要是虛函數,則它子類的相同函數也一定是虛函數,不過我們也要顯示的聲明來增加可讀性
- 在繼承時,不要重寫父類的非虛函數,否則靜態綁定后全都會使用父類的函數版本。
virtual void print() const
虛析構函數:沒有虛構造函數,但是有虛析構函數,為什么需要虛析構函數? 可能通過基類指針刪除派生類對象; 如果你打算允許其他人通過基類指針調用對象的析構函數(通過delete這樣做是正常的),就需要讓基類的析構函數成為虛函數,否則執行delete的結果是不確定的。
抽象類:多用作基類用于規范接口,只要有一個純虛函數的類就是抽象類,不能實例化,其中,純虛函數語法為
virtual void print() const = 0;
override和final的使用,override的使用可以使編譯器在編譯時進行檢查,以免發生難以調試的運行時錯誤,要習慣使用。
struct B4
{
virtual void g(int) {}
};
struct D4 : B4
{
virtual void g(int) override {} // OK
virtual void g(double) override {} // Error
};
struct B2
{
virtual void f() final {} // final 函數
};
struct D2 : B2
{
virtual void f() {}
};
- 注意區分虛基類和虛函數的作用,虛基類是為了消除多繼承中的二義性而引入的,而虛函數是為了實現多態性而引入的。
- 模板:編譯器幫我們的一種機制,使用時注意若要操作自定義類型時,請確保在類內重載了相應的運算符。
//函數模板
template <typename T>
T add(T x, T y){
return x+y;
}
//類模板
template <class T>
class Foo{
T element;
Foo();
}
Foo<T>::Foo(){}//注意此時在類外標注類名時要把模板參數帶上,寫成Foo<T>::
術語:概念
用來界定具備一定功能的數據類型。例如:
將“可以比大小的所有數據類型(有比較運算符)”這一概念記為Comparable
將“具有公有的復制構造函數并可以用‘=’賦值的數據類型”這一概念記為Assignable
將“可以比大小、具有公有的復制構造函數并可以用‘=’賦值的所有數據類型”這個概念記作Sortable
對于兩個不同的概念A和B,如果概念A所需求的所有功能也是概念B所需求的功能,那么就說概念B是概念A的子概念。例如:
Sortable既是Comparable的子概念,也是Assignable的子概念。
術語:模型
模型(model):符合一個概念的數據類型稱為該概念的模型,例如:
int型是Comparable概念的模型。
靜態數組類型不是Assignable概念的模型(無法用“=”給整個靜態數組賦值)STL:由迭代器,函數對象,容器,算法四部分組成
迭代器:從功能上可理解為一個泛型指針,
//求平方的函數
double square(double x) {
return x * x;
}
int main() {
//從標準輸入讀入若干個實數,分別將它們的平方輸出
transform(istream_iterator<double>(cin), istream_iterator<double>(),
ostream_iterator<double>(cout, "\t"), square);
cout << endl;
return 0;
}
//程序涉及到輸入迭代器、輸出迭代器、隨機訪問迭代器這三個迭代器概念,并且以前兩個概念為基礎編寫了一個通用算法。
#include <algorithm>
#include <iterator>
#include <vector>
#include <iostream>
using namespace std;
//將來自輸入迭代器的n個T類型的數值排序,將結果通過輸出迭代器result輸出
template <class T, class InputIterator, class OutputIterator>
void mySort(InputIterator first, InputIterator last, OutputIterator result) {
//通過輸入迭代器將輸入數據存入向量容器s中
vector<T> s;
for (;first != last; ++first)
s.push_back(*first);
//對s進行排序,sort函數的參數必須是隨機訪問迭代器
sort(s.begin(), s.end());
copy(s.begin(), s.end(), result); //將s序列通過輸出迭代器輸出
}
int main() {
//將s數組的內容排序后輸出
double a[5] = { 1.2, 2.4, 0.8, 3.3, 3.2 };
mySort<double>(a, a + 5, ostream_iterator<double>(cout, " "));
cout << endl;
//從標準輸入讀入若干個整數,將排序后的結果輸出
mySort<int>(istream_iterator<int>(cin), istream_iterator<int>(), ostream_iterator<int>(cout, " "));
cout << endl;
return 0;
}
/*
-
容器:
容器的分類.png
容器的通用功能
用默認構造函數構造空容器
支持關系運算符:==、!=、<、<=、>、>=
begin()、end():獲得容器首、尾迭代器
clear():將容器清空
empty():判斷容器是否為空
size():得到容器元素個數
s1.swap(s2):將s1和s2兩容器內容交換
相關數據類型(S表示容器類型)
S::iterator:指向容器元素的迭代器類型
S::const_iterator:常迭代器類型
可逆容器
STL為每個可逆容器都提供了逆向迭代器,逆向迭代器可以通過下面的成員函數得到:
rbegin() :指向容器尾的逆向迭代器
rend():指向容器首的逆向迭代器
逆向迭代器的類型名的表示方式如下:
S::reverse_iterator:逆向迭代器類型
S::constreverseiterator:逆向常迭代器類
隨機訪問容器
隨機訪問容器支持對容器的元素進行隨機訪問
s[n]:獲得容器s的第n個元素 順序容器的基本操作
#include <iostream>
#include <list>
#include <deque>
//輸出指定的順序容器的元素
template <class T>
void printContainer(const char* msg, const T& s) {
cout << msg << ": ";
copy(s.begin(), s.end(), ostream_iterator<int>(cout, " "));
cout << endl;
}
int main() {
//從標準輸入讀入10個整數,將它們分別從s的頭部加入
deque<int> s;
for (int i = 0; i < 10; i++) {
int x;
cin >> x;
s.push_front(x);
}
printContainer("deque at first", s);
//用s容器的內容的逆序構造列表容器l
list<int> l(s.rbegin(), s.rend());
printContainer("list at first", l);
//將列表容器l的每相鄰兩個元素順序顛倒
list<int>::iterator iter = l.begin();
while (iter != l.end()) {
int v = *iter;
iter = l.erase(iter);
l.insert(++iter, v);
}
printContainer("list at last", l);
//用列表容器l的內容給s賦值,將s輸出
s.assign(l.begin(), l.end());
printContainer("deque at last", s);
return 0;
}
int main() {
istream_iterator<int> i1(cin), i2; //建立一對輸入流迭代器
vector<int> s1(i1, i2); //通過輸入流迭代器從標準輸入流中輸入數據
sort(s1.begin(), s1.end()); //將輸入的整數排序
deque<int> s2;
//以下循環遍歷s1
for (vector<int>::iterator iter = s1.begin(); iter != s1.end(); ++iter)
{
if (*iter % 2 == 0) //偶數放到s2尾部
s2.push_back(*iter);
else //奇數放到s2首部
s2.push_front(*iter);
}
//將s2的結果輸出
copy(s2.begin(), s2.end(), ostream_iterator<int>(cout, " "));
cout << endl;
return 0;
}
STL所提供的順序容器各有所長也各有所短,我們在編寫程序時應當根據我們對容器所需要執行的操作來決定選擇哪一種容器。
如果需要執行大量的隨機訪問操作,而且當擴展容器時只需要向容器尾部加入新的元素,就應當選擇向量容器vector;
如果需要少量的隨機訪問操作,需要在容器兩端插入或刪除元素,則應當選擇雙端隊列容器deque;
如果不需要對容器進行隨機訪問,但是需要在中間位置插入或者刪除元素,就應當選擇列表容器list或forward_list;
如果需要數組,array相對于內置數組類型而言,是一種更安全、更容易使用的數組類型。
順序容器的插入迭代器
用于向容器頭部、尾部或中間指定位置插入元素的迭代器
包括前插迭代器(frontinserter)、后插迭代器(backinsrter)和任意位置插入迭代器(inserter).
- 集合
輸入一串實數,將重復的去掉,取最大和最小者的中值,分別輸出小于等于此中值和大于等于此中值的實數
//10_9.cpp
#include <set>
#include <iterator>
#include <utility>
#include <iostream>
using namespace std;
int main() {
set<double> s;
while (true) {
double v;
cin >> v;
if (v == 0) break; //輸入0表示結束
//嘗試將v插入
pair<set<double>::iterator,bool> r=s.insert(v);
if (!r.second) //如果v已存在,輸出提示信息
cout << v << " is duplicated" << endl;
}
//得到第一個元素的迭代器
set<double>::iterator iter1=s.begin();
//得到末尾的迭代器
set<double>::iterator iter2=s.end();
//得到最小和最大元素的中值
double medium=(*iter1 + *(--iter2)) / 2;
//輸出小于或等于中值的元素
cout<< "<= medium: "
copy(s.begin(), s.upper_bound(medium), ostream_iterator<double>(cout, " "));
cout << endl;
//輸出大于或等于中值的元素
cout << ">= medium: ";
copy(s.lower_bound(medium), s.end(), ostream_iterator<double>(cout, " "));
cout << endl;
return 0;
}
- map
統計一句話中每個字母出現的次數
// 10_11.cpp
#include <iostream>
#include <map>
#include <cctype>
using namespace std;
int main() {
map<char, int> s; //用來存儲字母出現次數的映射
char c; //存儲輸入字符
do {
cin >> c; //輸入下一個字符
if (isalpha(c)){ //判斷是否是字母
c = tolower(c); //將字母轉換為小寫
s[c]++; //將該字母的出現頻率加1
}
} while (c != '.'); //碰到“.”則結束輸入
//輸出每個字母出現次數
for (map<char, int>::iterator iter = s.begin(); iter != s.end(); ++iter)
cout << iter->first << " " << iter->second << " ";
cout << endl;
return 0;
}
- multiset 和multimap
//10_12.cpp
#include <iostream>
#include <map>
#include <utility>
#include <string>
using namespace std;
int main() {
multimap<string, string> courses;
typedef multimap<string, string>::iterator CourseIter;
//將課程上課時間插入courses映射中
courses.insert(make_pair("C++", "2-6"));
courses.insert(make_pair("COMPILER", "3-1"));
courses.insert(make_pair("COMPILER", "5-2"));
courses.insert(make_pair("OS", "1-2"));
courses.insert(make_pair("OS", "4-1"));
courses.insert(make_pair("OS", "5-5"));
//輸入一個課程名,直到找到該課程為止,記下每周上課次數
string name;
int count;
do {
cin >> name;
count = courses.count(name);
if (count == 0)
cout << "Cannot find this course!" << endl;
} while (count == 0);
//輸出每周上課次數和上課時間
cout << count << " lesson(s) per week: ";
pair<CourseIter, CourseIter> range = courses.equal_range(name);
for (CourseIter iter = range.first; iter != range.second; ++iter)
cout << iter->second << " ";
cout << endl;
return 0;
}
- 函數對象:
STL提供的函數對象
用于算術運算的函數對象:
一元函數對象(一個參數) :negate
二元函數對象(兩個參數) :plus、minus、multiplies、divides、modulus
用于關系運算、邏輯運算的函數對象(要求返回值為bool)
一元謂詞(一個參數):logical_not
二元謂詞(兩個參數):equalto、notequalto、greater、less、greaterequal、lessequal、logicaland、logical_or
#include <funtional>
sort(a.begin(), a.end(), greater<int>());
cout << accumulate(a, a + N, 1, multiplies<int>());
cout << accumulate(a, a + N, 1, mult)
- 函數適配器(感覺很難懂,不知怎么用,把代碼全都搞上來了)
- 綁定適配器:bind1st、bind2nd
將n元函數對象的指定參數綁定為一個常數,得到n-1元函數對象 - 組合適配器:not1、not2
將指定謂詞的結果取反 - 函數指針適配器:ptr_fun
將一般函數指針轉換為函數對象,使之能夠作為其它函數適配器的輸入。
在進行參數綁定或其他轉換的時候,通常需要函數對象的類型信息,例如bind1st和bind2nd要求函數對象必須繼承于binary_function類型。但如果傳入的是函數指針形式的函數對象,則無法獲得函數對象的類型信息。 - 成員函數適配器:ptrfun、ptrfun_ref
對成員函數指針使用,把n元成員函數適配為n + 1元函數對象,該函數對象的第一個參數為調用該成員函數時的目的對象
也就是需要將“object->method()”轉為“method(object)”形式。將“object->method(arg1)”轉為二元函數“method(object, arg1)”。
- 綁定適配器:bind1st、bind2nd
//數適配器實例——找到數組中第一個大于40的元素
int main() {
int intArr[] = { 30, 90, 10, 40, 70, 50, 20, 80 };
const int N = sizeof(intArr) / sizeof(int);
vector<int> a(intArr, intArr + N);
vector<int>::iterator p = find_if(a.begin(), a.end(), bind2nd(greater<int>(), 40));
if (p == a.end())
cout << "no element greater than 40" << endl;
else
cout << "first element greater than 40 is: " << *p << endl;
return 0;
}
注:
find_if算法在STL中的原型聲明為:
template<class InputIterator, class UnaryPredicate>
InputIterator find_if(InputIterator first, InputIterator last, UnaryPredicate pred);
它的功能是查找數組[first, last)區間中第一個pred(x)為真的元素。
//ptr_fun、not1和not2產生函數適配器實例
bool g(int x, int y) {
return x > y;
}
int main() {
int intArr[] = { 30, 90, 10, 40, 70, 50, 20, 80 };
const int N = sizeof(intArr) / sizeof(int);
vector<int> a(intArr, intArr + N);
vector<int>::iterator p;
p = find_if(a.begin(), a.end(), bind2nd(ptr_fun(g), 40));
if (p == a.end())
cout << "no element greater than 40" << endl;
else
cout << "first element greater than 40 is: " << *p << endl;
p = find_if(a.begin(), a.end(), not1(bind2nd(greater<int>(), 15)));
if (p == a.end())
cout << "no element is not greater than 15" << endl;
else
cout << "first element that is not greater than 15 is: " << *p << endl;
p = find_if(a.begin(), a.end(), bind2nd(not2(greater<int>()), 15));
if (p == a.end())
cout << "no element is not greater than 15" << endl;
else
cout << "first element that is not greater than 15 is: " << *p << endl;
return 0;
}
// 成員函數適配器實例
struct Car {
int id;
Car(int id) { this->id = id; }
void display() const { cout << "car " << id << endl; }
};
int main() {
vector<Car *> pcars;
vector<Car> cars;
for (int i = 0; i < 5; i++)
pcars.push_back(new Car(i));
for (int i = 5; i < 10; i++)
cars.push_back(Car(i));
cout << "elements in pcars: " << endl;
for_each(pcars.begin(), pcars.end(), std::mem_fun(&Car::display));
cout << endl;
cout << "elements in cars: " << endl;
for_each(cars.begin(), cars.end(), std::mem_fun_ref(&Car::display));
cout << endl;
for (size_t i = 0; i < pcars.size(); ++i)
delete pcars[i];
return 0;
}
- STL算法(有待于從C++primer上做理解性的補充)
STL算法分類
不可變序列算法
可變序列算法
排序和搜索算法
數值算法
<utility>頭文件,將>=, <=, >都轉化為對 < 的調用,!=則轉換為==的調用,因此我們在操作符重載時可以只重載 < 和=兩個操作符。同時要打開
using namespace std::rel_ops
刪除器代碼片段:可以結合for_each刪除區間內的所有指針
struct deleter{
template<class T>
void operator()(T* p){ delete p;}
};
for_each(container.begin(), container.end(), deleter());
- 唯一化數組元素并排序輸出的代碼段
sort(nums.begin(), nums.end());
auto end_unique = unique(nums.begin(), nums.end());
nums.erase(end_unique, nums.end());
copy(nums.begin(),nums.end(), ostream_iterator<int>(cout, "\n"));
substr(index, num)
表示從index開始截取num長的子串并返回count函數和count_if函數:功能類似于find。這個函數使用一對迭代器和一個值做參數,返回這個值出現次數的統計結果。
count(ivec.begin() , ivec.end() , searchValue)
bool greater10(int value)
{
return value >10;
}
result1 = count_if(v1.begin(), v1.end(), greater10);
- 輸出流
cout輸出的格式控制 :使用操縱符,除了一次性的外,多數操縱符作用時間是直到下一次狀態改變
#include <iomanip>
cout<<setw(10)<<nums[i]<<endl;//指定寬度,一次性的
cout << setiosflags(ios_base::left)<<nums[i]//左對齊
cout <<resetiosflags(ios_base::left)//取消左對齊的方式,恢復到默認右對齊,
cout<< setprecision(1) << values[i] << endl;//設置有效數字位數
cout<<setiosflags(ios_base::fixd)<< setprecision(1) << values[i] << endl;//設置有效數字
將流寫入二進制文件中:當待存文件無需供人閱讀時可以選用二進制的這種方式,讀入讀出效率都非常高
#include <fstream>
using namespace std;
struct Date {
int mon, day, year;
};
int main() {
Date dt = { 6, 10, 92 };
ofstream file("date.dat", ios_base::binary);
file.write(reinterpret_cast<char *>(&dt),sizeof(dt));
file.close();
return 0;
}
字符串輸出流:典型應用是將數值轉換為字符串,對于自定義類型,則必須重載相應的操作符如<<來使用
//11_6.cpp
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
//函數模板toString可以將各種支持“<<“插入符的類型的對象轉換為字符串。
template <class T>
inline string toString(const T &v) {
ostringstream os; //創建字符串輸出流
os << v; //將變量v的值寫入字符串流
return os.str(); //返回輸出流生成的字符串
}
int main() {
string str1 = toString(5);
cout << str1 << endl;
string str2 = toString(1.2);
cout << str2 << endl;
return 0;
}
- 輸入流: get 讀可以帶空字符,cin讀不出空字符,getline則可以讀出帶空格的字符串。
例11-7 get函數應用舉例
//11_7.cpp
#include <iostream>
using namespace std;
int main() {
char ch;
while ((ch = cin.get()) != EOF)//注意這里的EOF在unix下應該是crtl+Z那種東西
cout.put(ch);
return 0;
}
例11-8為輸入流指定一個終止字符:
//11_8.cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string line;
cout << "Type a line terminated by 't' " << endl;
getline(cin, line, 't');
cout << line << endl;
return 0;
}
例11-9 從文件讀一個二進制記錄到一個結構中
//11_9.cpp
#include <iostream>
#include <fstream>
#include <cstring>
using namespace std;
struct SalaryInfo {
unsigned id;
double salary;
};
int main() {
SalaryInfo employee1 = { 600001, 8000 };
ofstream os("payroll", ios_base::out | ios_base::binary);
os.write(reinterpret_cast<char *>(&employee1), sizeof(employee1));
os.close();
ifstream is("payroll", ios_base::in | ios_base::binary);
if (is) {
SalaryInfo employee2;
is.read(reinterpret_cast<char *>(&employee2), sizeof(employee2));
cout << employee2.id << " " << employee2.salary << endl;
} else {
cout << "ERROR: Cannot open file 'payroll'." << endl;
}
is.close();
return 0;
}
例11-10用seekg函數設置位置指針
//11_10.cpp, 頭部分省略
int main() {
int values[] = { 3, 7, 0, 5, 4 };
ofstream os("integers", ios_base::out | ios_base::binary);
os.write(reinterpret_cast<char *>(values), sizeof(values));
os.close();
ifstream is("integers", ios_base::in | ios_base::binary);
if (is) {
is.seekg(3 * sizeof(int));
int v;
is.read(reinterpret_cast<char *>(&v), sizeof(int));
cout << "The 4th integer in the file 'integers' is " << v << endl;
} else {
cout << "ERROR: Cannot open file 'integers'." << endl;
}
return 0;
}
例11-11 讀一個文件并顯示出其中0元素的位置
//11_11.cpp, 頭部分省略
int main() {
ifstream file("integers", ios_base::in | ios_base::binary);
if (file) {
while (file) {//讀到文件尾file為0
streampos here = file.tellg();
int v;
file.read(reinterpret_cast<char *>(&v), sizeof(int));
if (file && v == 0)
cout << "Position " << here << " is 0" << endl;
}
} else {
cout << "ERROR: Cannot open file 'integers'." << endl;
}
file.close();
return 0;
}
istringstream的使用
template <class T>
inline T fromString(const string &str) {
istringstream is(str); //創建字符串輸入流
T v;
is >> v; //從字符串輸入流中讀取變量v
return v; //返回變量v
}
int main() {
int v1 = fromString<int>("5");
cout << v1 << endl;
double v2 = fromString<double>("1.2");
cout << v2 << endl;
return 0;
}
輸出結果:
5
1.2
- setprecision(2)處理浮點數時會自動的進行四舍五入,如12.3456在setprecision(2)后是12.35,想要得到12.34怎么辦?用floor函數。
d=floor(d*100)/100;//處理后d=12.34
- 異常處理:首先要搞清楚為什么要引入異常處理,首先是為了讓錯誤處理更加靈活,你這個模塊沒有資格處理錯誤時怎么辦。其次是為了模塊化的設計,集中處理異常,不打擾程序邏輯。在大型應用程序中異常處理機制更能凸顯優勢,需要不斷應用學習。
//12_3.cpp
#include <iostream>
#include <cmath>
#include <stdexcept>
using namespace std;
//給出三角形三邊長,計算三角形面積
double area(double a, double b, double c) throw (invalid_argument)
{
//判斷三角形邊長是否為正
if (a <= 0 || b <= 0 || c <= 0)
throw invalid_argument("the side length should be positive");
//判斷三邊長是否滿足三角不等式
if (a + b <= c || b + c <= a || c + a <= b)
throw invalid_argument("the side length should fit the triangle inequation");
//由Heron公式計算三角形面積
double s = (a + b + c) / 2;
return sqrt(s * (s - a) * (s - b) * (s - c));
}
int main() {
double a, b, c; //三角形三邊長
cout << "Please input the side lengths of a triangle: ";
cin >> a >> b >> c;
try {
double s = area(a, b, c); //嘗試計算三角形面積
cout << "Area: " << s << endl;
} catch (exception &e) {
cout << "Error: " << e.what() << endl;
}
return 0;
}