一、文件和流
1.C++文件和流
iostream 標準庫,它提供了 cin 和 cout 方法分別用於從標準輸入讀取流和向標準輸出寫入流
C++ 中另一個標準庫 fstream,它定義了三個新的數據類型,介紹如何從文件讀取流和向文件寫入流具體如下:

數據類型

描述

ofstream

該數據類型表示輸出文件流,用於創建文件並向文件寫入信息

ifstream

該數據類型表示輸入文件流,用於從文件讀取信息

fstream

該數據類型表示文件流,且同時具有 ofstream 和 ifstream 兩種功能,這意味着它可以創建文件,向文件寫入信息,從文件讀取信息

要在 C++ 中進行文件處理,必須在 C++ 源代碼文件中包含頭文件 和 。
2.打開文件
在從文件讀取信息或者向文件寫入信息之前,必須先打開文件。ofstream 和 fstream 對象都可以用來打開文件進行寫操作,如果只需要打開文件進行讀操作,則使用 ifstream 對象。
下面是 open() 函數的標準語法,open() 函數是 fstream、ifstream 和 ofstream 對象的一個成員

void open(const char *filename, ios::openmode mode);

在這裏,open() 成員函數的第一參數指定要打開的文件的名稱和位置,第二個參數定義文件被打開的模式

模式標誌

描述

ios::app

追加模式,所有寫入都追加到文件的末尾。

ios::ate

文件打開後,定位到文件末尾。

ios::in

打開文件以讀取。

ios::out

打開文件以寫入。

ios::trunc

如果文件已經存在,其內容將在打開文件之前被截斷,即將文件長度設為0.

您可以把以上兩種或兩種以上的模式結合使用。例如,如果您想要以寫入模式打開文件,並希望截斷文件,以防文件已存在,那麼您可以使用下面的語法:

ofstream outfile;
outfile.open("file.dat", ios::out | ios::trunc );

類似地,您如果想要打開一個文件用於讀寫,可以使用下面的語法:

ifstream  afile;
afile.open("file.dat", ios::out | ios::in );

3.關閉文件
當 C++ 程序終止時,它會自動關閉刷新所有流,釋放所有分配的內存,並關閉所有打開的文件。但程序員應該養成一個好習慣,在程序終止前關閉所有打開的文件。
下面是 close() 函數的標準語法,close() 函數是 fstream、ifstream 和 ofstream 對象的一個成員。

void close();

4.寫入及其讀取文件
讀取文件
在 C++ 編程中,我們使用流插入運算符( << )向文件寫入信息,就像使用該運算符輸出信息到屏幕上一樣。唯一不同的是,在這裏您使用的是 ofstream 或 fstream 對象,而不是 cout 對象。
讀取文件
在 C++ 編程中,我們使用流提取運算符( >> )從文件讀取信息,就像使用該運算符從鍵盤輸入信息一樣。唯一不同的是,在這裏使用的是 ifstream 或 fstream 對象,而不是 cin 對象
5.讀寫文件實例
下面的 C++ 程序以讀寫模式打開一個文件。在向文件 afile.dat 寫入用户輸入的信息之後,程序從文件讀取信息,並將其輸出到屏幕上:

#include <fstream>
#include <iostream>
using namespace std;
 
int main ()
{
 
   char data[100];
 
   // 以寫模式打開文件
   ofstream outfile;
   outfile.open("afile.dat");
 
   cout << "Writing to the file" << endl;
   cout << "Enter your name: ";
   cin.getline(data, 100);
 
   // 向文件寫入用户輸入的數據
   outfile << data << endl;
 
   cout << "Enter your age: ";
   cin >> data;
   cin.ignore();
 
   // 再次向文件寫入用户輸入的數據
   outfile << data << endl;
 
   // 關閉打開的文件
   outfile.close();
 
   // 以讀模式打開文件
   ifstream infile;
   infile.open("afile.dat");
 
   cout << "Reading from the file" << endl;
   infile >> data;
 
   // 在屏幕上寫入數據
   cout << data << endl;
 
   // 再次從文件讀取數據,並顯示它
   infile >> data;
   cout << data << endl;
 
   // 關閉打開的文件
   infile.close();
 
   return 0;
}

當上面的代碼被編譯和執行時,它會產生下列輸入和輸出:

$./a.out
Writing to the file
Enter your name: Zara
Enter your age: 9
Reading from the file
Zara
9

上面的實例中使用了 cin 對象的附加函數,比如 getline()函數從外部讀取一行,ignore() 函數會忽略掉之前讀語句留下的多餘字符。
6.文件位置指針
istream 和 ostream 都提供了用於重新定位文件位置指針的成員函數。這些成員函數包括關於 istream 的 seekg(“seek get”)和關於 ostream 的 seekp(“seek put”)。
seekg 和 seekp 的參數通常是一個長整型。第二個參數可以用於指定查找方向。查找方向可以是 ios::beg(默認的,從流的開頭開始定位),也可以是 ios::cur(從流的當前位置開始定位),也可以是 ios::end(從流的末尾開始定位)。
文件位置指針是一個整數值,指定了從文件的起始位置到指針所在位置的字節數,下面是關於定位 “get” 文件位置指針的實例:

// 定位到 fileObject 的第 n 個字節(假設是 ios::beg)
fileObject.seekg( n );
// 把文件的讀指針從 fileObject 當前位置向後移 n 個字節
fileObject.seekg( n, ios::cur );
// 把文件的讀指針從 fileObject 末尾往回移 n 個字節
fileObject.seekg( n, ios::end );
// 定位到 fileObject 的末尾
fileObject.seekg( 0, ios::end );

二、C++ 異常處理
1.C++異常處理
C++ 異常是指在程序運行時發生的特殊情況,比如嘗試除以零的操作。
異常提供了一種轉移程序控制權的方式。C++ 異常處理涉及到三個關鍵字:try、catch、throw。
throw: 當問題出現時,程序會拋出一個異常。這是通過使用 throw 關鍵字來完成的。
catch: 在您想要處理問題的地方,通過異常處理程序捕獲異常,catch 關鍵字用於捕獲異常。
try: try 塊中的代碼標識將被激活的特定異常。它後面通常跟着一個或多個 catch 塊。
如果有一個塊拋出一個異常,捕獲異常的方法會使用 try 和 catch 關鍵字。try 塊中放置可能拋出異常的代碼try 塊中的代碼被稱為保護代碼
使用 try/catch 語句的語法如下所示:

try
{
   // 保護代碼
}catch( ExceptionName e1 )
{
   // catch 塊
}catch( ExceptionName e2 )
{
   // catch 塊
}catch( ExceptionName eN )
{
   // catch 塊
}

如果 try 塊在不同的情境下會拋出不同的異常,這個時候可以嘗試羅列多個 catch 語句,用於捕獲不同類型的異常。
2.拋出異常
您可以使用 throw 語句在代碼塊中的任何地方拋出異常。throw 語句的操作數可以是任意的表達式,表達式的結果的類型決定了拋出的異常的類型。
以下是嘗試除以零時拋出異常的實例

double division(int a, int b)
{
   if( b == 0 )
   {
      throw "Division by zero condition!";
   }
   return (a/b);
}

3.捕獲異常
catch 塊跟在 try 塊後面,用於捕獲異常。您可以指定想要捕捉的異常類型,這是由 catch 關鍵字後的括號內的異常聲明決定的。

try
{
   // 保護代碼
}catch( ExceptionName e )
{
  // 處理 ExceptionName 異常的代碼
}

上面的代碼會捕獲一個類型為 ExceptionName 的異常。如果您想讓 catch 塊能夠處理 try 塊拋出的任何類型的異常,則必須在異常聲明的括號內使用省略號 …,如下所示:

try
{
   // 保護代碼
}catch(...)
{
  // 能處理任何異常的代碼
}

下面是一個實例,拋出一個除以零的異常,並在 catch 塊中捕獲該異常。

#include <iostream>
using namespace std;
double division(int a, int b)
{
   if( b == 0 )
   {
      throw "Division by zero condition!";
   }
   return (a/b);
}
 
int main ()
{
   int x = 50;
   int y = 0;
   double z = 0;

   try {
     z = division(x, y);
     cout << z << endl;
   }catch (const char* msg) {
     cerr << msg << endl;
   }
 
   return 0;
}

由於我們拋出了一個類型為 const char* 的異常,因此,當捕獲該異常時,我們必須在 catch 塊中使用 const char*。當上面的代碼被編譯和執行時,它會產生下列結果:

Division by zero condition!

4.定義新的異常
定義新的異常
您可以**通過繼承和重載 exception 類來定義新的異常。**下面的實例演示瞭如何使用 std::exception 類來實現自己的異常:

#include <iostream>
#include <exception>
using namespace std;
 
struct MyException : public exception
{
  const char * what () const throw ()
  {
    return "C++ Exception";
  }
};
 
int main()
{
  try
  {
    throw MyException();
  }
  catch(MyException& e)
  {
    std::cout << "MyException caught" << std::endl;
    std::cout << e.what() << std::endl;
  }
  catch(std::exception& e)
  {
    //其他的錯誤
  }
}

這將產生以下結果:

MyException caught
C++ Exception

在這裏,what() 是異常類提供的一個公共方法,它已被所有子異常類重載。這將返回異常產生的原因。
三、動態內存
1.C++動態內存
2.new 和 delete 運算符
下面是使用 new 運算符來為任意的數據類型動態分配內存的通用語法:

new data-type;

在這裏,data-type 可以是包括數組在內的任意內置的數據類型,也可以是包括類或結構在內的用户自定義的任何數據類型。讓我們先來看下內置的數據類型。例如,我們可以定義一個指向 double 類型的指針,然後請求內存,該內存在執行時被分配。我們可以按照下面的語句使用 new 運算符來完成這點:

double* pvalue  = NULL; // 初始化為 null 的指針
pvalue  = new double;   // 為變量請求內存

如果自由存儲區已被用完,可能無法成功分配內存。所以建議檢查 new 運算符是否返回 NULL 指針,並採取以下適當的操作:

double* pvalue  = NULL;
if( !(pvalue  = new double ))
{
   cout << "Error: out of memory." <<endl;
   exit(1);
 
}

malloc() 函數在 C 語言中就出現了,在 C++ 中仍然存在,但建議儘量不要使用 malloc() 函數。new 與 malloc() 函數相比,其主要的優點是,new 不只是分配了內存,它還創建了對象。

在任何時候,當您覺得某個已經動態分配內存的變量不再需要使用時,您可以使用 delete 操作符釋放它所佔用的內存,如下所示:

delete pvalue;        // 釋放 pvalue 所指向的內存

下面的實例中使用了上面的概念,演示瞭如何使用 new 和 delete 運算符:

#include <iostream>
using namespace std;
 
int main ()
{
   double* pvalue  = NULL; // 初始化為 null 的指針
   pvalue  = new double;   // 為變量請求內存
 
   *pvalue = 29494.99;     // 在分配的地址存儲值
   cout << "Value of pvalue : " << *pvalue << endl;
 
   delete pvalue;         // 釋放內存
 
   return 0;
}

當上面的代碼被編譯和執行時,它會產生下列結果:

Value of pvalue : 29495

3.數組的動態內存分配
假設我們要為一個字符數組(一個有 20 個字符的字符串)分配內存,我們可以使用上面實例中的語法來為數組動態地分配內存,如下所示:

char* pvalue  = NULL;   // 初始化為 null 的指針
pvalue  = new char[20]; // 為變量請求內存

要刪除我們剛才創建的數組,語句如下:

delete [] pvalue;        // 刪除 pvalue 所指向的數組

下面是 new 操作符的通用語法,可以為多維數組分配內存,如下所示:
一維數組

// 動態分配,數組長度為 m
int *array=new int [m];
 
//釋放內存
delete [] array;

二維數組

int **array
// 假定數組第一維長度為 m, 第二維長度為 n
// 動態分配空間
array = new int *[m];
for( int i=0; i<m; i++ )
{
    array[i] = new int [n]  ;
}
//釋放
for( int i=0; i<m; i++ )
{
    delete [] arrary[i];
}
delete [] array;

二維數組實例測試:

#include <iostream>
using namespace std;
 
int main()
{
    int **p;  
    int i,j;   //p[4][8]
    //開始分配4行8列的二維數據  
    p = new int *[4];
    for(i=0;i<4;i++){
        p[i]=new int [8];
    }
 
    for(i=0; i<4; i++){
        for(j=0; j<8; j++){
            p[i][j] = j*i;
        }
    }  
    //打印數據  
    for(i=0; i<4; i++){
        for(j=0; j<8; j++)    
        {  
            if(j==0) cout<<endl;  
            cout<<p[i][j]<<"\t";  
        }
    }  
    //開始釋放申請的堆  
    for(i=0; i<4; i++){
        delete [] p[i];  
    }
    delete [] p;  
    return 0;
}

三維數組

int ***array;
// 假定數組第一維為 m, 第二維為 n, 第三維為h
// 動態分配空間
array = new int **[m];
for( int i=0; i<m; i++ )
{
    array[i] = new int *[n];
    for( int j=0; j<n; j++ )
    {
        array[i][j] = new int [h];
    }
}
//釋放
for( int i=0; i<m; i++ )
{
    for( int j=0; j<n; j++ )
    {
        delete[] array[i][j];
    }
    delete[] array[i];
}
delete[] array;

三維數組測試實例:

#include <iostream>
using namespace std;
 
int main()
{  
    int i,j,k;   // p[2][3][4]
 
    int ***p;
    p = new int **[2];
    for(i=0; i<2; i++)
    {
        p[i]=new int *[3];
        for(j=0; j<3; j++)
            p[i][j]=new int[4];
    }
 
    //輸出 p[i][j][k] 三維數據
    for(i=0; i<2; i++)  
    {
        for(j=0; j<3; j++)  
        {
            for(k=0;k<4;k++)
            {
                p[i][j][k]=i+j+k;
                cout<<p[i][j][k]<<" ";
            }
            cout<<endl;
        }
        cout<<endl;
    }
 
    // 釋放內存
    for(i=0; i<2; i++)
    {
        for(j=0; j<3; j++)
        {  
            delete [] p[i][j];  
        }  
    }      
    for(i=0; i<2; i++)  
    {      
        delete [] p[i];  
    }  
    delete [] p; 
    return 0;
}

4.對象的內存分配
對象與簡單的數據類型沒有什麼不同。例如,請看下面的代碼,我們將使用一個對象數組來理清這一概念

#include <iostream>
using namespace std;
 
class Box
{
   public:
      Box() {
         cout << "調用構造函數!" <<endl;
      }
      ~Box() {
         cout << "調用析構函數!" <<endl;
      }
};
 
int main( )
{
   Box* myBoxArray = new Box[4];
 
   delete [] myBoxArray; // 刪除數組
   return 0;
}

如果要為一個包含四個 Box 對象的數組分配內存,構造函數將被調用 4 次,同樣地,當刪除這些對象時,析構函數也將被調用相同的次數(4次)。
當上面的代碼被編譯和執行時,它會產生下列結果:

調用構造函數!
調用構造函數!
調用構造函數!
調用構造函數!
調用析構函數!
調用析構函數!
調用析構函數!
調用析構函數!

四、C++命名空間
1.定義命名空間
命名空間的定義使用關鍵字 namespace,後跟命名空間的名稱,如下所示:

namespace namespace_name {
   // 代碼聲明
}

為了調用帶有命名空間的函數或變量,需要在前面加上命名空間的名稱,如下所示:

name::code;  // code 可以是變量或函數

讓我們來看看命名空間如何為變量或函數等實體定義範圍:

#include <iostream>
using namespace std;
 
// 第一個命名空間
namespace first_space{
   void func(){
      cout << "Inside first_space" << endl;
   }
}
// 第二個命名空間
namespace second_space{
   void func(){
      cout << "Inside second_space" << endl;
   }
}
int main ()
{
 
   // 調用第一個命名空間中的函數
   first_space::func();
 
   // 調用第二個命名空間中的函數
   second_space::func();
 
   return 0;
}

當上面的代碼被編譯和執行時,它會產生下列結果:

Inside first_space
Inside second_space

2.using 指令
您可以使用 using namespace 指令,這樣在使用命名空間時就可以不用在前面加上命名空間的名稱。這個指令會告訴編譯器,後續的代碼將使用指定的命名空間中的名稱

include <iostream>
using namespace std;

// 第一個命名空間
namespace first_space{
   void func(){
      cout << "Inside first_space" << endl;
   }
}
// 第二個命名空間
namespace second_space{
   void func(){
      cout << "Inside second_space" << endl;
   }
}
using namespace first_space;
int main ()
{
 
   // 調用第一個命名空間中的函數
   func();
 
   return 0;
}

當上面的代碼被編譯和執行時,它會產生下列結果:

Inside first_space

using 指令也可以用來指定命名空間中的特定項目。例如,如果您只打算使用 std 命名空間中的 cout 部分,您可以使用如下的語句:

using std::cout;

隨後的代碼中,在使用 cout 時就可以不用加上命名空間名稱作為前綴,但是 std 命名空間中的其他項目仍然需要加上命名空間名稱作為前綴,如下所示:

#include <iostream>
using std::cout;
 
int main ()
{
 
   cout << "std::endl is used with std!" << std::endl;
 
   return 0;
}

當上面的代碼被編譯和執行時,它會產生下列結果:

std::endl is used with std!

using 指令引入的名稱遵循正常的範圍規則。名稱從使用 using 指令開始是可見的,直到該範圍結束。此時,在範圍以外定義的同名實體是隱藏的。

3.不連續的命名空間
命名空間可以定義在幾個不同的部分中,因此命名空間是由幾個單獨定義的部分組成的。一個命名空間的各個組成部分可以分散在多個文件中。

所以,如果命名空間中的某個組成部分需要請求定義在另一個文件中的名稱,則仍然需要聲明該名稱。下面的命名空間定義可以是定義一個新的命名空間,也可以是為已有的命名空間增加新的元素:

namespace namespace_name {
   // 代碼聲明
}

4.嵌套的命名空間
命名空間可以嵌套,您可以在一個命名空間中定義另一個命名空間,如下所示:

namespace namespace_name1 {
   // 代碼聲明
   namespace namespace_name2 {
      // 代碼聲明
   }
}

您可以通過使用 :: 運算符來訪問嵌套的命名空間中的成員

// 訪問 namespace_name2 中的成員
using namespace namespace_name1::namespace_name2;
 
// 訪問 namespace:name1 中的成員
using namespace namespace_name1;

在上面的語句中,如果使用的是 namespace_name1,那麼在該範圍內 namespace_name2 中的元素也是可用的,如下所示:

#include <iostream>
using namespace std;
 
// 第一個命名空間
namespace first_space{
   void func(){
      cout << "Inside first_space" << endl;
   }
   // 第二個命名空間
   namespace second_space{
      void func(){
         cout << "Inside second_space" << endl;
      }
   }
}
using namespace first_space::second_space;
int main ()
{
 
   // 調用第二個命名空間中的函數
   func();
 
   return 0;
}

當上面的代碼被編譯和執行時,它會產生下列結果:

Inside second_space

五、C++模版
1.C++ 模板
模板是泛型編程的基礎,泛型編程即以一種獨立於任何特定類型的方式編寫代碼。
模板是創建泛型類或函數的藍圖或公式。庫容器,比如迭代器和算法,都是泛型編程的例子,它們都使用了模板的概念。
每個容器都有一個單一的定義,比如 向量,我們可以定義許多不同類型的向量,比如 vector 或 vector
您可以使用模板來定義函數和類,接下來讓我們一起來看看如何使用。
2.函數模板
模板函數定義的一般形式如下所示:

template <class type> ret-type func-name(parameter list)
{
   // 函數的主體
}

在這裏,type 是函數所使用的數據類型的佔位符名稱。這個名稱可以在函數定義中使用。
下面是函數模板的實例,返回兩個數中的最大值:

#include <iostream>
#include <string>
 
using namespace std;
 
template <typename T>
inline T const& Max (T const& a, T const& b)
{
    return a < b ? b:a;
}
int main ()
{
 
    int i = 39;
    int j = 20;
    cout << "Max(i, j): " << Max(i, j) << endl;
 
    double f1 = 13.5;
    double f2 = 20.7;
    cout << "Max(f1, f2): " << Max(f1, f2) << endl;
 
    string s1 = "Hello";
    string s2 = "World";
    cout << "Max(s1, s2): " << Max(s1, s2) << endl;
 
   return 0;
}

當上面的代碼被編譯和執行時,它會產生下列結果:

Max(i, j): 39
Max(f1, f2): 20.7
Max(s1, s2): World

3.類模板
正如我們定義函數模板一樣,我們也可以定義類模板。泛型類聲明的一般形式如下所示:

template <class type> class class-name {
.
.
.
}

在這裏,type 是佔位符類型名稱,可以在類被實例化的時候進行指定。您可以使用一個逗號分隔的列表來定義多個泛型數據類型。

下面的實例定義了類 Stack<>,並實現了泛型方法來對元素進行入棧出棧操作

#include <iostream>
#include <vector>
#include <cstdlib>
#include <string>
#include <stdexcept>
 
using namespace std;
 
template <class T>
class Stack {
  private:
    vector<T> elems;     // 元素
 
  public:
    void push(T const&);  // 入棧
    void pop();               // 出棧
    T top() const;            // 返回棧頂元素
    bool empty() const{       // 如果為空則返回真。
        return elems.empty();
    }
};
 
template <class T>
void Stack<T>::push (T const& elem)
{
    // 追加傳入元素的副本
    elems.push_back(elem);   
}
 
template <class T>
void Stack<T>::pop ()
{
    if (elems.empty()) {
        throw out_of_range("Stack<>::pop(): empty stack");
    }
    // 刪除最後一個元素
    elems.pop_back();        
}
 
template <class T>
T Stack<T>::top () const
{
    if (elems.empty()) {
        throw out_of_range("Stack<>::top(): empty stack");
    }
    // 返回最後一個元素的副本
    return elems.back();     
}
 
int main()
{
    try {
        Stack<int>         intStack;  // int 類型的棧
        Stack<string> stringStack;    // string 類型的棧
 
        // 操作 int 類型的棧
        intStack.push(7);
        cout << intStack.top() <<endl;
 
        // 操作 string 類型的棧
        stringStack.push("hello");
        cout << stringStack.top() << std::endl;
        stringStack.pop();
        stringStack.pop();
    }
    catch (exception const& ex) {
        cerr << "Exception: " << ex.what() <<endl;
        return -1;
    }
}

當上面的代碼被編譯和執行時,它會產生下列結果:

7
hello
Exception: Stack<>::pop(): empty stack

六、C++預處理器
1.C++ 預處理器
預處理器是一些指令,指示編譯器在實際編譯之前所需完成的預處理。
所有的預處理器指令都是以井號(#)開頭,只有空格字符可以出現在預處理指令之前。預處理指令不是 C++ 語句,所以它們不會以分號(;)結尾。
我們已經看到,之前所有的實例中都有 #include 指令。這個宏用於把頭文件包含到源文件中。
C++ 還支持很多預處理指令,比如 #include、#define、#if、#else、#line 等,讓我們一起看看這些重要指令。
2.#define 預處理
#define 預處理指令用於創建符號常量。該符號常量通常稱為宏,指令的一般形式是:

#define macro-name replacement-text

當這一行代碼出現在一個文件中時,在該文件中後續出現的所有宏都將會在程序編譯之前被替換為 replacement-text。例如:

#include <iostream>
using namespace std;
 
#define PI 3.14159
 
int main ()
{
 
    cout << "Value of PI :" << PI << endl;
 
    return 0;
}

現在,讓我們測試這段代碼,看看預處理的結果。假設源代碼文件已經存在,接下來使用 -E 選項進行編譯,並把結果重定向到 test.p。現在,如果您查看 test.p 文件,將會看到它已經包含大量的信息,而且在文件底部的值被改為如下:

$ gcc -E test.cpp > test.p

 
...
int main ()
{
 
    cout << "Value of PI :" << 3.14159 << endl;
 
    return 0;
}

3.參數宏
您可以使用 #define 來定義一個帶有參數的宏,如下所示:

#include <iostream>
using namespace std;
 
#define MIN(a,b) (a<b ? a : b)
 
int main ()
{
   int i, j;
   i = 100;
   j = 30;
   cout <<"較小的值為:" << MIN(i, j) << endl;
 
    return 0;
}

當上面的代碼被編譯和執行時,它會產生下列結果:

較小的值為:30

4.條件編譯
有幾個指令可以用來有選擇地對部分程序源代碼進行編譯。這個過程被稱為條件編譯。
條件預處理器的結構與 if 選擇結構很像。請看下面這段預處理器的代碼:

#ifdef NULL
   #define NULL 0
#endif

您可以只在調試時進行編譯,調試開關可以使用一個宏來實現,如下所示:

#ifdef DEBUG
   cerr <<"Variable x = " << x << endl;
#endif

如果在指令 #ifdef DEBUG 之前已經定義了符號常量 DEBUG,則會對程序中的 cerr 語句進行編譯。您可以使用 #if 0 語句註釋掉程序的一部分,如下所示:

#if 0
   不進行編譯的代碼
#endif

讓我們嘗試下面的實例:

#include <iostream>
using namespace std;
#define DEBUG
 
#define MIN(a,b) (((a)<(b)) ? a : b)
 
int main ()
{
   int i, j;
   i = 100;
   j = 30;
#ifdef DEBUG
   cerr <<"Trace: Inside main function" << endl;
#endif
 
#if 0
   /* 這是註釋部分 */
   cout << MKSTR(HELLO C++) << endl;
#endif
 
   cout <<"The minimum is " << MIN(i, j) << endl;
 
#ifdef DEBUG
   cerr <<"Trace: Coming out of main function" << endl;
#endif
    return 0;
}

當上面的代碼被編譯和執行時,它會產生下列結果:

Trace: Inside main function
The minimum is 30
Trace: Coming out of main function

5…# 和 ## 運算符
(1)# 和 ## 預處理運算符在 C++ 和 ANSI/ISO C 中都是可用的。# 運算符會把 replacement-text 令牌轉換為用引號引起來的字符串。
請看下面的宏定義:

#include <iostream>
using namespace std;
 
#define MKSTR( x ) #x
 
int main ()
{
    cout << MKSTR(HELLO C++) << endl;
 
    return 0;
}

當上面的代碼被編譯和執行時,它會產生下列結果:
HELLO C++
讓我們來看看它是如何工作的。不難理解,C++ 預處理器把下面這行:

cout << MKSTR(HELLO C++) << endl;

轉換成了:

cout << "HELLO C++" << endl;

運算符用於連接兩個令牌。下面是一個實例:

#define CONCAT( x, y )  x ## y

當 CONCAT 出現在程序中時,它的參數會被連接起來,並用來取代宏。例如,程序中 CONCAT(HELLO, C++) 會被替換為 “HELLO C++”,如下面實例所示。

#include <iostream>
using namespace std;
 
#define concat(a, b) a ## b
int main()
{
   int xy = 100;
 
   cout << concat(x, y);
   return 0;
}

當上面的代碼被編譯和執行時,它會產生下列結果:

100

讓我們來看看它是如何工作的。不難理解,C++ 預處理器把下面這行:

cout << concat(x, y);
轉換成了:
cout << xy;

6.C++ 中的預定義宏
C++ 提供了下表所示的一些預定義宏:


描述

LINE

這會在程序編譯時包含當前行號。

FILE

這會在程序編譯時包含當前文件名。

DATE

這會包含一個形式為 month/day/year 的字符串,它表示把源文件轉換為目標代碼的日期

TIME

這會包含一個形式為 hour:minute:second 的字符串,它表示程序被編譯的時間

讓我們看看上述這些宏的實例:

#include <iostream>
using namespace std;
 
int main ()
{
    cout << "Value of __LINE__ : " << __LINE__ << endl;
    cout << "Value of __FILE__ : " << __FILE__ << endl;
    cout << "Value of __DATE__ : " << __DATE__ << endl;
    cout << "Value of __TIME__ : " << __TIME__ << endl;
 
    return 0;
}

當上面的代碼被編譯和執行時,它會產生下列結果:

Value of __LINE__ : 6
Value of __FILE__ : test.cpp
Value of __DATE__ : Feb 28 2011
Value of __TIME__ : 18:52:48

七、C++信號處理

  1. C++ 信號處理
    信號是由操作系統傳給進程的中斷,會提早終止一個程序。在 UNIX、LINUX、Mac OS X 或 Windows 系統上,可以通過按 Ctrl+C 產生中斷。
    有些信號不能被程序捕獲,但是下表所列信號可以在程序中捕獲,並可以基於信號採取適當的動作。這些信號是定義在 C++ 頭文件 中。

信號

描述

SIGABRT

程序的異常終止,如調用 abort。

SIGFPE

錯誤的算術運算,比如除以零或導致溢出的操作。

SIGILL

檢測非法指令。

SIGINT

接收到交互注意信號。

SIGSEGV

非法訪問內存。

SIGTERM

發送到程序的終止請求。

2.signal() 函數
C++ 信號處理庫提供了 signal 函數,用來捕獲突發事件。以下是 signal() 函數的語法:

void (*signal (int sig, void (*func)(int)))(int);

這個函數接收兩個參數:第一個參數是一個整數,代表了信號的編號;第二個參數是一個指向信號處理函數的指針。

讓我們編寫一個簡單的 C++ 程序,使用 signal() 函數捕獲 SIGINT 信號。不管您想在程序中捕獲什麼信號,您都必須使用 signal 函數來註冊信號,並將其與信號處理程序相關聯。看看下面的實例:

#include <iostream>
#include <csignal>
#include <unistd.h>
 
using namespace std;
 
void signalHandler( int signum )
{
    cout << "Interrupt signal (" << signum << ") received.\n";
 
    // 清理並關閉
    // 終止程序 
 
   exit(signum); 
 
}
int main ()
{
    // 註冊信號 SIGINT 和信號處理程序
    signal(SIGINT, signalHandler); 
 
    while(1){
       cout << "Going to sleep...." << endl;
       sleep(1);
    }
 
    return 0;
}

當上面的代碼被編譯和執行時,它會產生下列結果:

Going to sleep....
Going to sleep....
Going to sleep....

現在,按 Ctrl+C 來中斷程序,您會看到程序捕獲信號,程序打印如下內容並退出:

Going to sleep....
Going to sleep....
Going to sleep....
Interrupt signal (2) received.

3.raise() 函數
您可以使用函數 raise() 生成信號,該函數帶有一個整數信號編號作為參數,語法如下:

int raise (signal sig);

在這裏,sig 是要發送的信號的編號,這些信號包括:SIGINT、SIGABRT、SIGFPE、SIGILL、SIGSEGV、SIGTERM、SIGHUP。以下是我們使用 raise() 函數內部生成信號的實例:

#include <iostream>
#include <csignal>
#include <unistd.h>
 
using namespace std;
 
void signalHandler( int signum )
{
    cout << "Interrupt signal (" << signum << ") received.\n";
 
    // 清理並關閉
    // 終止程序
 
   exit(signum); 
 
}
 
int main ()
{
    int i = 0;
    // 註冊信號 SIGINT 和信號處理程序
    signal(SIGINT, signalHandler); 
 
    while(++i){
       cout << "Going to sleep...." << endl;
       if( i == 3 ){
          raise( SIGINT);
       }
       sleep(1);
    }
 
    return 0;
}

當上面的代碼被編譯和執行時,它會產生下列結果,並會自動退出:

Going to sleep....
Going to sleep....
Going to sleep....
Interrupt signal (2) received.

八、C++多線程

  1. C++ 多線程
    多線程是多任務處理的一種特殊形式,多任務處理允許讓電腦同時運行兩個或兩個以上的程序。一般情況下,兩種類型的多任務處理:基於進程和基於線程

基於進程的多任務處理是程序的併發執行
基於線程的多任務處理是同一程序的片段的併發執行
多線程程序包含可以同時運行的兩個或多個部分。這樣的程序中的每個部分稱為一個線程,每個線程定義了一個單獨的執行路徑。
本教程假設您使用的是 Linux 操作系統,我們要使用 POSIX 編寫多線程 C++ 程序。POSIX Threads 或 Pthreads 提供的 API 可在多種類 Unix POSIX 系統上可用,比如 FreeBSD、NetBSD、GNU/Linux、Mac OS X 和 Solaris。

2.創建線程
下面的程序,我們可以用它來創建一個 POSIX 線程:

#include <pthread.h>
pthread_create (thread, attr, start_routine, arg)

在這裏,pthread_create 創建一個新的線程,並讓它可執行。下面是關於參數的説明:

參數 描述
thread 指向線程標識符指針。
attr 一個不透明的屬性對象,可以被用來設置線程屬性。您可以指定線程屬性對象,也可以使用默認值 NULL。
start_routine 線程運行函數起始地址,一旦線程被創建就會執行。
arg 運行函數的參數。它必須通過把引用作為指針強制轉換為 void 類型進行傳遞。如果沒有傳遞參數,則使用 NULL。
創建線程成功時,函數返回 0,若返回值不為 0 則説明創建線程失敗。

3.終止線程
使用下面的程序,我們可以用它來終止一個 POSIX 線程:

#include <pthread.h>
pthread_exit (status)

在這裏,pthread_exit 用於顯式地退出一個線程。通常情況下,pthread_exit() 函數是在線程完成工作後無需繼續存在時被調用。

如果 main() 是在它所創建的線程之前結束,並通過 pthread_exit() 退出,那麼其他線程將繼續執行。否則,它們將在 main() 結束時自動被終止。

實例
以下簡單的實例代碼使用 pthread_create() 函數創建了 5 個線程,每個線程輸出"Hello Nowcoder!":

#include <iostream>
// 必須的頭文件
#include <pthread.h>
 
using namespace std;
 
#define NUM_THREADS 5
 
// 線程的運行函數
void* say_hello(void* args)
{
    cout << "Hello Nowcoder!" << endl;
    return 0;
}
 
int main()
{
    // 定義線程的 id 變量,多個變量使用數組
    pthread_t tids[NUM_THREADS];
    for(int i = 0; i < NUM_THREADS; ++i)
    {
        //參數依次是:創建的線程id,線程參數,調用的函數,傳入的函數參數
        int ret = pthread_create(&tids[i], NULL, say_hello, NULL);
        if (ret != 0)
        {
           cout << "pthread_create error: error_code=" << ret << endl;
        }
    }
    //等各個線程退出後,進程才結束,否則進程強制結束了,線程可能還沒反應過來;
    pthread_exit(NULL);
}

使用 -lpthread 庫編譯下面的程序:

$ g++ test.cpp -lpthread -o test.o

現在,執行程序,將產生下列結果:

$ ./test.o
Hello Nowcoder!
Hello Nowcoder!
Hello Nowcoder!
Hello Nowcoder!
Hello Nowcoder!

以下簡單的實例代碼使用 pthread_create() 函數創建了 5 個線程,並接收傳入的參數。每個線程打印一個 “Hello Nowcoder!” 消息,並輸出接收的參數,然後調用 pthread_exit() 終止線程

//文件名:test.cpp
 
#include <iostream>
#include <cstdlib>
#include <pthread.h>
 
using namespace std;
 
#define NUM_THREADS     5
 
void *PrintHello(void *threadid)
{ 
   // 對傳入的參數進行強制類型轉換,由無類型指針變為整形數指針,然後再讀取
   int tid = *((int*)threadid);
   cout << "Hello Nowcoder! 線程 ID, " << tid << endl;
   pthread_exit(NULL);
}
 
int main ()
{
   pthread_t threads[NUM_THREADS];
   int indexes[NUM_THREADS];// 用數組來保存i的值
   int rc;
   int i;
   for( i=0; i < NUM_THREADS; i++ ){     
      cout << "main() : 創建線程, " << i << endl;
      indexes[i] = i; //先保存i的值
      // 傳入的時候必須強制轉換為void* 類型,即無類型指針       
      rc = pthread_create(&threads[i], NULL,
                          PrintHello, (void *)&(indexes[i]));
      if (rc){
         cout << "Error:無法創建線程," << rc << endl;
         exit(-1);
      }
   }
   pthread_exit(NULL);
}

現在編譯並執行程序,將產生下列結果:

$ g++ test.cpp -lpthread -o test.o
$ ./test.o
main() : 創建線程, 0
main() : 創建線程, 1
Hello Nowcoder! 線程 ID, 0
main() : 創建線程, Hello Nowcoder! 線程 ID, 21

main() : 創建線程, 3
Hello Nowcoder! 線程 ID, 2
main() : 創建線程, 4
Hello Nowcoder! 線程 ID, 3
Hello Nowcoder! 線程 ID, 4

4.向線程傳遞參數
這個實例演示瞭如何通過結構傳遞多個參數。您可以在線程回調中傳遞任意的數據類型,因為它指向 void,如下面的實例所示:

#include <iostream>
#include <cstdlib>
#include <pthread.h>
 
using namespace std;
 
#define NUM_THREADS     5
 
struct thread_data{
   int  thread_id;
   char *message;
};
 
void *PrintHello(void *threadarg)
{
   struct thread_data *my_data;
 
   my_data = (struct thread_data *) threadarg;
 
   cout << "Thread ID : " << my_data->thread_id ;
   cout << " Message : " << my_data->message << endl;
 
   pthread_exit(NULL);
}
 
int main ()
{
   pthread_t threads[NUM_THREADS];
   struct thread_data td[NUM_THREADS];
   int rc;
   int i;
 
   for( i=0; i < NUM_THREADS; i++ ){
      cout <<"main() : creating thread, " << i << endl;
      td[i].thread_id = i;
      td[i].message = (char*)"This is message";
      rc = pthread_create(&threads[i], NULL,
                          PrintHello, (void *)&td[i]);
      if (rc){
         cout << "Error:unable to create thread," << rc << endl;
         exit(-1);
      }
   }
   pthread_exit(NULL);
}

當上面的代碼被編譯和執行時,它會產生下列結果:

$ g++ -Wno-write-strings test.cpp -lpthread -o test.o
$ ./test.o
main() : creating thread, 0
main() : creating thread, 1
Thread ID : 0 Message : This is message
main() : creating thread, Thread ID : 21
 Message : This is message
main() : creating thread, 3
Thread ID : 2 Message : This is message
main() : creating thread, 4
Thread ID : 3 Message : This is message
Thread ID : 4 Message : This is message

5.連接和分離線程
我們可以使用以下兩個函數來連接或分離線程

pthread_join (threadid, status)
pthread_detach (threadid)

pthread_join() 子程序阻礙調用程序,直到指定的 threadid 線程終止為止。當創建一個線程時,它的某個屬性會定義它是否是可連接的(joinable)或可分離的(detached)。只有創建時定義為可連接的線程才可以被連接。如果線程創建時被定義為可分離的,則它永遠也不能被連接。
這個實例演示瞭如何使用 pthread_join() 函數來等待線程的完成。

#include <iostream>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>
 
using namespace std;
 
#define NUM_THREADS     5
 
void *wait(void *t)
{
   int i;
   long tid;
 
   tid = (long)t;
 
   sleep(1);
   cout << "Sleeping in thread " << endl;
   cout << "Thread with id : " << tid << "  ...exiting " << endl;
   pthread_exit(NULL);
}
 
int main ()
{
   int rc;
   int i;
   pthread_t threads[NUM_THREADS];
   pthread_attr_t attr;
   void *status;
 
   // 初始化並設置線程為可連接的(joinable)
   pthread_attr_init(&attr);
   pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
 
   for( i=0; i < NUM_THREADS; i++ ){
      cout << "main() : creating thread, " << i << endl;
      rc = pthread_create(&threads[i], NULL, wait, (void *)&i );
      if (rc){
         cout << "Error:unable to create thread," << rc << endl;
         exit(-1);
      }
   }
 
   // 刪除屬性,並等待其他線程
   pthread_attr_destroy(&attr);
   for( i=0; i < NUM_THREADS; i++ ){
      rc = pthread_join(threads[i], &status);
      if (rc){
         cout << "Error:unable to join," << rc << endl;
         exit(-1);
      }
      cout << "Main: completed thread id :" << i ;
      cout << "  exiting with status :" << status << endl;
   }
 
   cout << "Main: program exiting." << endl;
   pthread_exit(NULL);
}

當上面的代碼被編譯和執行時,它會產生下列結果:

main() : creating thread, 0
main() : creating thread, 1
main() : creating thread, 2
main() : creating thread, 3
main() : creating thread, 4
Sleeping in thread
Thread with id : 4  ...exiting
Sleeping in thread
Thread with id : 3  ...exiting
Sleeping in thread
Thread with id : 2  ...exiting
Sleeping in thread
Thread with id : 1  ...exiting
Sleeping in thread
Thread with id : 0  ...exiting
Main: completed thread id :0  exiting with status :0
Main: completed thread id :1  exiting with status :0
Main: completed thread id :2  exiting with status :0
Main: completed thread id :3  exiting with status :0
Main: completed thread id :4  exiting with status :0
Main: program exiting.

九、C++ web編程

  1. C++ Web 編程
    什麼是 CGI?
    公共網關接口(CGI),是一套標準,定義了信息是如何在 Web 服務器和客户端腳本之間進行交換的。
    CGI 規範目前是由 NCSA 維護的,NCSA 定義 CGI 如下:
    公共網關接口(CGI),是一種用於外部網關程序與信息服務器(如 HTTP 服務器)對接的接口標準。
    Web 瀏覽
    為了更好地瞭解 CGI 的概念,讓我們點擊一個超鏈接,瀏覽一個特定的網頁或 URL,看看會發生什麼。

您的瀏覽器聯繫上 HTTP Web 服務器,並請求 URL,即文件名。
Web 服務器將解析 URL,並查找文件名。如果找到請求的文件,Web 服務器會把文件發送回瀏覽器,否則發送一條錯誤消息,表明您請求了一個錯誤的文件。
Web 瀏覽器從 Web 服務器獲取響應,並根據接收到的響應來顯示文件或錯誤消息。
然而,以這種方式搭建起來的 HTTP 服務器,不管何時請求目錄中的某個文件,HTTP 服務器發送回來的不是該文件,而是以程序形式執行,並把執行產生的輸出發送回瀏覽器顯示出來。

公共網關接口(CGI),是使得應用程序(稱為 CGI 程序或 CGI 腳本)能夠與 Web 服務器以及客户端進行交互的標準協議。這些 CGI 程序可以用 Python、PERL、Shell、C 或 C++ 等進行編寫。

CGI 架構圖
下圖演示了 CGI 的架構:

Web 服務器配置
在您進行 CGI 編程之前,請確保您的 Web 服務器支持 CGI,並已配置成可以處理 CGI 程序。所有由 HTTP 服務器執行的 CGI 程序,都必須在預配置的目錄中。該目錄稱為 CGI 目錄,按照慣例命名為 /var/www/cgi-bin。雖然 CGI 文件是 C++ 可執行文件,但是按照慣例它的擴展名是 .cgi。

默認情況下,Apache Web 服務器會配置在 /var/www/cgi-bin 中運行 CGI 程序。如果您想指定其他目錄來運行 CGI 腳本,您可以在 httpd.conf 文件中修改以下部分:

<Directory "/var/www/cgi-bin">
   AllowOverride None
   Options ExecCGI
   Order allow,deny
   Allow from all
</Directory>
 
<Directory "/var/www/cgi-bin">
Options All
</Directory>

在這裏,我們假設已經配置好 Web 服務器並能成功運行,你可以運行任意的 CGI 程序,比如 Perl 或 Shell 等。

3.第一個 CGI 程序
請看下面的 C++ 程序:

#include <iostream>
using namespace std;
 
int main ()
{
 
   cout << "Content-type:text/html\r\n\r\n";
   cout << "<html>\n";
   cout << "<head>\n";
   cout << "<title>Hello World - 第一個 CGI 程序</title>\n";
   cout << "</head>\n";
   cout << "<body>\n";
   cout << "<h2>Hello World! 這是我的第一個 CGI 程序</h2>\n";
   cout << "</body>\n";
   cout << "</html>\n";
 
   return 0;
}

編譯上面的代碼,把可執行文件命名為 cplusplus.cgi,並把這個文件保存在 /var/www/cgi-bin 目錄中。在運行 CGI 程序之前,請使用 chmod 755 cplusplus.cgi UNIX 命令來修改文件模式,確保文件可執行。訪問可執行文件,您會看到下面的輸出:

Hello World! 這是我的第一個 CGI 程序

上面的 C++ 程序是一個簡單的程序,把它的輸出寫在 STDOUT 文件上,即顯示在屏幕上。在這裏,值得注意一點,第一行輸出 Content-type:text/html\r\n\r\n。這一行發送回瀏覽器,並指定要顯示在瀏覽器窗口上的內容類型。您必須理解 CGI 的基本概念,這樣才能進一步使用 Python 編寫更多複雜的 CGI 程序。C++ CGI 程序可以與任何其他外部的系統(如 RDBMS)進行交互。

4.HTTP 頭信息
行 Content-type:text/html\r\n\r\n 是 HTTP 頭信息的組成部分,它被髮送到瀏覽器,以便更好地理解頁面內容。HTTP 頭信息的形式如下:

HTTP 字段名稱: 字段內容
例如
Content-type: text/html\r\n\r\n
還有一些其他的重要的 **HTTP 頭信息**,這些在您的 CGI 編程中都會經常被用到。

頭信息

描述

Content-type:

MIME 字符串,定義返回的文件格式。例如 Content-type:text/html。

Expires:

Date 信息變成無效的日期。瀏覽器使用它來判斷一個頁面何時需要刷新。一個有效的日期字符串的格式應為 01 Jan 1998 12:00:00 GMT。

Location:

URL 這個 URL 是指應該返回的 URL,而不是請求的 URL。你可以使用它來重定向一個請求到任意的文件。

Last-modified:

Date 資源的最後修改日期。

Content-length:

N 要返回的數據的長度,以字節為單位。瀏覽器使用這個值來表示一個文件的預計下載時間。

Set-Cookie:

String 通過 string 設置 cookie。

CGI 環境變量
所有的 CGI 程序都可以訪問下列的環境變量。這些變量在編寫 CGI 程序時扮演了非常重要的角色。

變量名

描述

CONTENT_TYPE

內容的數據類型。當客户端向服務器發送附加內容時使用。例如,文件上傳等功能。

CONTENT_LENGTH

查詢的信息長度。只對 POST 請求可用。

HTTP_COOKIE

以鍵 & 值對的形式返回設置的 cookies。

HTTP_USER_AGENT

用户代理請求標頭字段,遞交用户發起請求的有關信息,包含了瀏覽器的名稱、版本和其他平台性的附加信息。

PATH_INFO

CGI 腳本的路徑。

QUERY_STRING

通過 GET 方法發送請求時的 URL 編碼信息,包含 URL 中問號後面的參數。

REMOTE_ADDR

發出請求的遠程主機的 IP 地址。這在日誌記錄和認證時是非常有用的。

REMOTE_HOST

發出請求的主機的完全限定名稱。如果此信息不可用,則可以用 REMOTE_ADDR 來獲取 IP 地址。

REQUEST_METHOD

用於發出請求的方法。最常見的方法是 GET 和 POST。

SCRIPT_FILENAME

CGI 腳本的完整路徑。

SCRIPT_NAME

CGI 腳本的名稱。

SERVER_NAME

服務器的主機名或 IP 地址。

SERVER_SOFTWARE

服務器上運行的軟件的名稱和版本。

下面的 CGI 程序列出了所有的 CGI 變量。

#include <iostream>
#include <stdlib.h>
#include <string>
using namespace std;
 
const string ENV[ 24 ] = {                
        "COMSPEC", "DOCUMENT_ROOT", "GATEWAY_INTERFACE",  
        "HTTP_ACCEPT", "HTTP_ACCEPT_ENCODING",            
        "HTTP_ACCEPT_LANGUAGE", "HTTP_CONNECTION",        
        "HTTP_HOST", "HTTP_USER_AGENT", "PATH",           
        "QUERY_STRING", "REMOTE_ADDR", "REMOTE_PORT",     
        "REQUEST_METHOD", "REQUEST_URI", "SCRIPT_FILENAME",
        "SCRIPT_NAME", "SERVER_ADDR", "SERVER_ADMIN",     
        "SERVER_NAME","SERVER_PORT","SERVER_PROTOCOL",    
        "SERVER_SIGNATURE","SERVER_SOFTWARE" };  
 
int main ()
{
 
   cout << "Content-type:text/html\r\n\r\n";
   cout << "<html>\n";
   cout << "<head>\n";
   cout << "<title>CGI 環境變量</title>\n";
   cout << "</head>\n";
   cout << "<body>\n";
   cout << "<table border = \"0\" cellspacing = \"2\">";
 
   for ( int i = 0; i < 24; i++ )
   {
       cout << "<tr><td>" << ENV[ i ] << "</td><td>";
       // 嘗試檢索環境變量的值
       char *value = getenv( ENV[ i ].c_str() ); 
       if ( value != 0 ){
         cout << value;                                
       }else{
         cout << "環境變量不存在。";
       }
       cout << "</td></tr>\n";
   }
   cout << "</table><\n";
   cout << "</body>\n";
   cout << "</html>\n";
 
   return 0;
}

5.C++ CGI 庫
在真實的實例中,您需要通過 CGI 程序執行許多操作。這裏有一個專為 C++ 程序而編寫的 CGI 庫,我們可以從 ftp://ftp.gnu.org/gnu/cgicc/ 上下載這個 CGI 庫,並按照下面的步驟安裝庫:

$ tar xzf cgicc-X.X.X.tar.gz
$ cd cgicc-X.X.X/
$ ./configure --prefix=/usr
$ make
$ make install

注意:libcgicc.so 和 libcgicc.a 庫會被安裝到/usr/lib目錄下,需執行拷貝命令:

$ sudo cp /usr/lib/libcgicc.* /usr/lib64/

才能使 CGI 程序自動找到 libcgicc.so 動態鏈接庫。
您可以點擊 C++ CGI Lib Documentation,查看相關的庫文檔。

6.GET 和 POST 方法
您可能有遇到過這樣的情況,當您需要從瀏覽器傳遞一些信息到 Web 服務器,最後再傳到 CGI 程序。通常瀏覽器會使用兩種方法把這個信息傳到 Web 服務器,分別是 GET 和 POST 方法。

使用 GET 方法傳遞信息
GET 方法發送已編碼的用户信息追加到頁面請求中。頁面和已編碼信息通過 ? 字符分隔開,如下所示: GET 方法是默認的從瀏覽器向 Web 服務器傳信息的方法,它會在瀏覽器的地址欄中生成一串很長的字符串。當您向服務器傳密碼或其他一些敏感信息時,不要使用 GET 方法。GET 方法有大小限制,在一個請求字符串中最多可以傳 1024 個字符。

當使用 GET 方法時,是使用 QUERY_STRING http 頭來傳遞信息,在 CGI 程序中可使用 QUERY_STRING 環境變量來訪問。

您可以通過在 URL 後跟上簡單連接的鍵值對,也可以通過使用 HTML

標籤的 GET 方法來傳信息。 簡單的 URL 實例:Get 方法 下面是一個簡單的 URL,使用 GET 方法傳遞兩個值給 hello_get.py 程序。

/cgi-bin/cpp_get.cgi?first_name=ZARA&last_name=ALI

下面的實例生成 cpp_get.cgi CGI 程序,用於處理 Web 瀏覽器給出的輸入。通過使用 C++ CGI 庫,可以很容易地訪問傳遞的信息:

#include <iostream>
#include <vector> 
#include <string> 
#include <stdio.h> 
#include <stdlib.h>
 
#include <cgicc/CgiDefs.h>
#include <cgicc/Cgicc.h>
#include <cgicc/HTTPHTMLHeader.h>
#include <cgicc/HTMLClasses.h> 
 
using namespace std;
using namespace cgicc;
 
int main ()
{
   Cgicc formData;
 
   cout << "Content-type:text/html\r\n\r\n";
   cout << "<html>\n";
   cout << "<head>\n";
   cout << "<title>使用 GET 和 POST 方法</title>\n";
   cout << "</head>\n";
   cout << "<body>\n";
 
   form_iterator fi = formData.getElement("first_name"); 
   if( !fi->isEmpty() && fi != (*formData).end()) { 
      cout << "名:" << **fi << endl; 
   }else{
      cout << "No text entered for first name" << endl; 
   }
   cout << "<br/>\n";
   fi = formData.getElement("last_name"); 
   if( !fi->isEmpty() &&fi != (*formData).end()) { 
      cout << "姓:" << **fi << endl; 
   }else{
      cout << "No text entered for last name" << endl; 
   }
   cout << "<br/>\n";
 
   cout << "</body>\n";
   cout << "</html>\n";
 
   return 0;

}
現在,編譯上面的程序,如下所示:

$g++ -o cpp_get.cgi cpp_get.cpp -lcgicc

生成 cpp_get.cgi,並把它放在 CGI 目錄中,並嘗試使用下面的鏈接進行訪問:

/cgi-bin/cpp_get.cgi?first_name=ZARA&last_name=ALI

這會產生以下結果:

名:ZARA
姓:ALI

7.簡單的表單實例:GET 方法
下面是一個簡單的實例,使用 HTML 表單和提交按鈕傳遞兩個值。我們將使用相同的 CGI 腳本 cpp_get.cgi 來處理輸入。

<form action="/cgi-bin/cpp_get.cgi" method="get">
名:<input type="text" name="first_name">  <br />
 
姓:<input type="text" name="last_name" />
<input type="submit" value="提交" />
</form>

下面是上述表單的實際輸出,請輸入名和姓,然後點擊提交按鈕查看結果。

使用 POST 方法傳遞信息
一個更可靠的向 CGI 程序傳遞信息的方法是 POST 方法。這種方法打包信息的方式與 GET 方法相同,不同的是,它不是把信息以文本字符串形式放在 URL 中的 ? 之後進行傳遞,而是把它以單獨的消息形式進行傳遞。該消息是以標準輸入的形式傳給 CGI 腳本的。

我們同樣使用 cpp_get.cgi 程序來處理 POST 方法。讓我們以同樣的例子,通過使用 HTML 表單和提交按鈕來傳遞兩個值,只不過這次我們使用的不是 GET 方法,而是 POST 方法,如下所示:

<form action="/cgi-bin/cpp_get.cgi" method="post">
名:<input type="text" name="first_name"><br />
姓:<input type="text" name="last_name" />
 
<input type="submit" value="提交" />
</form>

向 CGI 程序傳遞複選框數據
當需要選擇多個選項時,我們使用複選框。

下面的 HTML 代碼實例是一個帶有兩個複選框的表單:

<form action="/cgi-bin/cpp_checkbox.cgi"
         method="POST"
         target="_blank">
<input type="checkbox" name="maths" value="on" /> 數學
<input type="checkbox" name="physics" value="on" /> 物理
<input type="submit" value="選擇學科" />
</form>

下面的 C++ 程序會生成 cpp_checkbox.cgi 腳本,用於處理 Web 瀏覽器通過複選框給出的輸入。

#include <iostream>
#include <vector> 
#include <string> 
#include <stdio.h> 
#include <stdlib.h>
 
#include <cgicc/CgiDefs.h>
#include <cgicc/Cgicc.h>
#include <cgicc/HTTPHTMLHeader.h>
#include <cgicc/HTMLClasses.h>
 
using namespace std;
using namespace cgicc;
 
int main ()
{
   Cgicc formData;
   bool maths_flag, physics_flag;
 
   cout << "Content-type:text/html\r\n\r\n";
   cout << "<html>\n";
   cout << "<head>\n";
   cout << "<title>向 CGI 程序傳遞複選框數據</title>\n";
   cout << "</head>\n";
   cout << "<body>\n";
 
   maths_flag = formData.queryCheckbox("maths");
   if( maths_flag ) { 
      cout << "Maths Flag: ON " << endl; 
   }else{
      cout << "Maths Flag: OFF " << endl; 
   }
   cout << "<br/>\n";
 
   physics_flag = formData.queryCheckbox("physics");
   if( physics_flag ) { 
      cout << "Physics Flag: ON " << endl; 
   }else{
      cout << "Physics Flag: OFF " << endl; 
   }
   cout << "<br/>\n";
   cout << "</body>\n";
   cout << "</html>\n";
 
   return 0;
}

向 CGI 程序傳遞單選按鈕數據
當只需要選擇一個選項時,我們使用單選按鈕。

下面的 HTML 代碼實例是一個帶有兩個單選按鈕的表單:

<form action="/cgi-bin/cpp_radiobutton.cgi"
         method="post"
         target="_blank">
<input type="radio" name="subject" value="maths"
                                    checked="checked"/> 數學
<input type="radio" name="subject" value="physics" /> 物理
<input type="submit" value="選擇學科" />
</form>

下面的 C++ 程序會生成 cpp_radiobutton.cgi 腳本,用於處理 Web 瀏覽器通過單選按鈕給出的輸入。

#include <iostream>
#include <vector> 
#include <string> 
#include <stdio.h> 
#include <stdlib.h>
 
#include <cgicc/CgiDefs.h>
#include <cgicc/Cgicc.h>
#include <cgicc/HTTPHTMLHeader.h>
#include <cgicc/HTMLClasses.h>
 
using namespace std;
using namespace cgicc;
 
int main ()
{
   Cgicc formData;
 
   cout << "Content-type:text/html\r\n\r\n";
   cout << "<html>\n";
   cout << "<head>\n";
   cout << "<title>向 CGI 程序傳遞單選按鈕數據</title>\n";
   cout << "</head>\n";
   cout << "<body>\n";
 
   form_iterator fi = formData.getElement("subject"); 
   if( !fi->isEmpty() && fi != (*formData).end()) { 
      cout << "Radio box selected: " << **fi << endl; 
   }
 
   cout << "<br/>\n";
   cout << "</body>\n";
   cout << "</html>\n";
 
   return 0;
}

向 CGI 程序傳遞文本區域數據
當需要向 CGI 程序傳遞多行文本時,我們使用 TEXTAREA 元素。

下面的 HTML 代碼實例是一個帶有 TEXTAREA 框的表單:

<form action="/cgi-bin/cpp_textarea.cgi"
         method="post"
         target="_blank">
<textarea name="textcontent" cols="40" rows="4">
請在這裏輸入文本...
</textarea>
<input type="submit" value="提交" />
</form>

下面的 C++ 程序會生成 cpp_textarea.cgi 腳本,用於處理 Web 瀏覽器通過文本區域給出的輸入。

#include <iostream>
#include <vector> 
#include <string> 
#include <stdio.h> 
#include <stdlib.h>
 
#include <cgicc/CgiDefs.h>
#include <cgicc/Cgicc.h>
#include <cgicc/HTTPHTMLHeader.h>
#include <cgicc/HTMLClasses.h>
 
using namespace std;
using namespace cgicc;
 
int main ()
{
   Cgicc formData;
 
   cout << "Content-type:text/html\r\n\r\n";
   cout << "<html>\n";
   cout << "<head>\n";
   cout << "<title>向 CGI 程序傳遞文本區域數據</title>\n";
   cout << "</head>\n";
   cout << "<body>\n";
 
   form_iterator fi = formData.getElement("textcontent"); 
   if( !fi->isEmpty() && fi != (*formData).end()) { 
      cout << "Text Content: " << **fi << endl; 
   }else{
      cout << "No text entered" << endl; 
   }
 
   cout << "<br/>\n";
   cout << "</body>\n";
   cout << "</html>\n";
 
   return 0;
}

向 CGI 程序傳遞下拉框數據
當有多個選項可用,但只能選擇一個或兩個選項時,我們使用下拉框。

下面的 HTML 代碼實例是一個帶有下拉框的表單:

<form action="/cgi-bin/cpp_dropdown.cgi"
                       method="post" target="_blank">
<select name="dropdown">
<option value="Maths" selected>數學</option>
<option value="Physics">物理</option>
</select>
<input type="submit" value="提交"/>
</form>

下面的 C++ 程序會生成 cpp_dropdown.cgi 腳本,用於處理 Web 瀏覽器通過下拉框給出的輸入。

#include <iostream>
#include <vector> 
#include <string> 
#include <stdio.h> 
#include <stdlib.h>
 
#include <cgicc/CgiDefs.h>
#include <cgicc/Cgicc.h>
#include <cgicc/HTTPHTMLHeader.h>
#include <cgicc/HTMLClasses.h>
 
using namespace std;
using namespace cgicc;
 
int main ()
{
   Cgicc formData;
 
   cout << "Content-type:text/html\r\n\r\n";
   cout << "<html>\n";
   cout << "<head>\n";
   cout << "<title>向 CGI 程序傳遞下拉框數據</title>\n";
   cout << "</head>\n";
   cout << "<body>\n";
 
   form_iterator fi = formData.getElement("dropdown"); 
   if( !fi->isEmpty() && fi != (*formData).end()) { 
      cout << "Value Selected: " << **fi << endl; 
   }
 
   cout << "<br/>\n";
   cout << "</body>\n";
   cout << "</html>\n";
 
   return 0;
}

10.在 CGI 中使用 Cookies
HTTP 協議是一種無狀態的協議。但對於一個商業網站,它需要在不同頁面間保持會話信息。例如,一個用户在完成多個頁面的步驟之後結束註冊。但是,如何在所有網頁中保持用户的會話信息。
在許多情況下,使用 cookies 是記憶和跟蹤有關用户喜好、購買、佣金以及其他為追求更好的遊客體驗或網站統計所需信息的最有效的方法。

它是如何工作的
服務器以 cookie 的形式向訪客的瀏覽器發送一些數據。如果瀏覽器接受了 cookie,則 cookie 會以純文本記錄的形式存儲在訪客的硬盤上。現在,當訪客訪問網站上的另一個頁面時,會檢索 cookie。一旦找到 cookie,服務器就知道存儲了什麼。

cookie 是一種純文本的數據記錄,帶有 5 個可變長度的字段:

Expires : cookie 的過期日期。如果此字段留空,cookie 會在訪客退出瀏覽器時過期。
Domain : 網站的域名。
Path : 設置 cookie 的目錄或網頁的路徑。如果您想從任意的目錄或網頁檢索 cookie,此字段可以留空。
Secure : 如果此字段包含單詞 “secure”,那麼 cookie 只能通過安全服務器進行檢索。如果此字段留空,則不存在該限制。
Name=Value : cookie 以鍵值對的形式被設置和獲取。
設置 Cookies
向瀏覽器發送 cookies 是非常簡單的。這些 cookies 會在 Content-type 字段之前,與 HTTP 頭一起被髮送。假設您想設置 UserID 和 Password 為 cookies,設置 cookies 的步驟如下所示:

#include <iostream>
using namespace std;
 
int main ()
{
 
   cout << "Set-Cookie:UserID=XYZ;\r\n";
   cout << "Set-Cookie:Password=XYZ123;\r\n";
   cout << "Set-Cookie:Domain=www.w3cschool.cc;\r\n";
   cout << "Set-Cookie:Path=/perl;\n";
   cout << "Content-type:text/html\r\n\r\n";
 
   cout << "<html>\n";
   cout << "<head>\n";
   cout << "<title>CGI 中的 Cookies</title>\n";
   cout << "</head>\n";
   cout << "<body>\n";
 
   cout << "設置 cookies" << endl; 
 
   cout << "<br/>\n";
   cout << "</body>\n";
   cout << "</html>\n";
 
   return 0;
}

從這個實例中,我們瞭解瞭如何設置 cookies。我們使用 Set-Cookie HTTP 頭來設置 cookies。

在這裏,有一些設置 cookies 的屬性是可選的,比如 Expires、Domain 和 Path。值得注意的是,cookies 是在發送行 "Content-type:text/html\r\n\r\n 之前被設置的。

編譯上面的程序,生成 setcookies.cgi,並嘗試使用下面的鏈接設置 cookies。它會在您的計算機上設置四個 cookies:

/cgi-bin/setcookies.cgi

獲取 Cookies
檢索所有設置的 cookies 是非常簡單的。cookies 被存儲在 CGI 環境變量 HTTP_COOKIE 中,且它們的形式如下:

key1=value1;key2=value2;key3=value3....

下面的實例演示瞭如何獲取 cookies。

#include <iostream>
#include <vector> 
#include <string> 
#include <stdio.h> 
#include <stdlib.h>
 
#include <cgicc/CgiDefs.h>
#include <cgicc/Cgicc.h>
#include <cgicc/HTTPHTMLHeader.h>
#include <cgicc/HTMLClasses.h>
 
using namespace std;
using namespace cgicc;
 
int main ()
{
   Cgicc cgi;
   const_cookie_iterator cci;
 
   cout << "Content-type:text/html\r\n\r\n";
   cout << "<html>\n";
   cout << "<head>\n";
   cout << "<title>CGI 中的 Cookies</title>\n";
   cout << "</head>\n";
   cout << "<body>\n";
   cout << "<table border = \"0\" cellspacing = \"2\">";
 
   // 獲取環境變量
   const CgiEnvironment& env = cgi.getEnvironment();
 
   for( cci = env.getCookieList().begin();
        cci != env.getCookieList().end();
        ++cci )
   {
      cout << "<tr><td>" << cci->getName() << "</td><td>";
      cout << cci->getValue();                                
      cout << "</td></tr>\n";
   }
   cout << "</table><\n";
 
   cout << "<br/>\n";
   cout << "</body>\n";
   cout << "</html>\n";
 
   return 0;
}

現在,編譯上面的程序,生成 getcookies.cgi,並嘗試使用下面的鏈接獲取您的計算機上所有可用的 cookies:

/cgi-bin/getcookies.cgi

這會產生一個列表,顯示了上一節中設置的四個 cookies 以及您的計算機上所有其他的 cookies:

UserID XYZ
Password XYZ123
Domain www.nowcoder.com
Path /perl

文件上傳實例
為了上傳一個文件,HTML 表單必須把 enctype 屬性設置為 multipart/form-data。帶有文件類型的 input 標籤會創建一個 “Browse” 按鈕。

<html>
<body>
   <form enctype="multipart/form-data"
            action="/cgi-bin/cpp_uploadfile.cgi"
            method="post">
   <p>文件:<input type="file" name="userfile" /></p>
   <p><input type="submit" value="上傳" /></p>
   </form>
</body>
</html>

注意:上面的實例已經故意禁用了保存上傳的文件在我們的服務器上。您可以在自己的服務器上嘗試上面的代碼。

下面是用於處理文件上傳的腳本 cpp_uploadfile.cpp:

#include <iostream>
#include <vector> 
#include <string> 
#include <stdio.h> 
#include <stdlib.h>
 
#include <cgicc/CgiDefs.h>
#include <cgicc/Cgicc.h>
#include <cgicc/HTTPHTMLHeader.h>
#include <cgicc/HTMLClasses.h>
 
using namespace std;
using namespace cgicc;
 
int main ()
{
   Cgicc cgi;
 
   cout << "Content-type:text/html\r\n\r\n";
   cout << "<html>\n";
   cout << "<head>\n";
   cout << "<title>CGI 中的文件上傳</title>\n";
   cout << "</head>\n";
   cout << "<body>\n";
 
   // 獲取要被上傳的文件列表
   const_file_iterator file = cgi.getFile("userfile");
   if(file != cgi.getFiles().end()) {
      // 在 cout 中發送數據類型
      cout << HTTPContentHeader(file->getDataType());
      // 在 cout 中寫入內容
      file->writeToStream(cout);
   }
   cout << "<文件上傳成功>\n";
   cout << "</body>\n";
   cout << "</html>\n";
 
   return 0;
}

上面的實例是在 cout 流中寫入內容,但您可以打開文件流,並把上傳的文件內容保存在目標位置的某個文件中。