C 进阶
参考文档:C 教程
C 文件和流数据类型描述ofstream该数据类型表示输出文件流,用于创建文件并向文件写入信息。ifstream该数据类型表示输入文件流,用于从文件读取信息。fstream该数据类型通常表示文件流,且同时具有 ofstream 和 ifstream 两种功能,这意味着它可以创建文件,向文件写入信息,从文件读取信息。
要在 C 中进行文件处理,必须在 C 源代码文件中包含头文件 <iostream> 和 <fstream>。
打开文件
在从文件读取信息或者向文件写入信息之前,必须先打开文件。ofstream 和 fstream 对象都可以用来打开文件进行写操作,如果只需要打开文件进行读操作,则使用 ifstream 对象。
下面是 open() 函数的标准语法,open() 函数是 fstream、ifstream 和 ofstream 对象的一个成员。
voidopen(constchar*filename,ios::openmodemode);
在这里,open() 成员函数的第一参数指定要打开的文件的名称和位置,第二个参数定义文件被打开的模式。
模式标志描述ios::app追加模式。所有写入都追加到文件末尾。ios::ate文件打开后定位到文件末尾。ios::in打开文件用于读取。ios::out打开文件用于写入。ios::trunc如果该文件已经存在,其内容将在打开文件之前被截断,即把文件长度设为 0。
您可以把以上两种或两种以上的模式结合使用。例如,如果您想要以写入模式打开文件,并希望截断文件,以防文件已存在,那么您可以使用下面的语法:
ofstreamoutfile;outfile.open(“file.dat”,ios::out|ios::trunc);
类似地,您如果想要打开一个文件用于读写,可以使用下面的语法:
fstreamafile;afile.open(“file.dat”,ios::out|ios::in);关闭文件
当 C 程序终止时,它会自动关闭刷新所有流,释放所有分配的内存,并关闭所有打开的文件。但程序员应该养成一个好习惯,在程序终止前关闭所有打开的文件。
下面是 close() 函数的标准语法,close() 函数是 fstream、ifstream 和 ofstream 对象的一个成员。
voidclose();写入文件
在 C 编程中,我们使用流插入运算符( << )向文件写入信息,就像使用该运算符输出信息到屏幕上一样。唯一不同的是,在这里您使用的是 ofstream 或 fstream 对象,而不是 cout 对象。
读取文件
在 C 编程中,我们使用流提取运算符( >> )从文件读取信息,就像使用该运算符从键盘输入信息一样。唯一不同的是,在这里您使用的是 ifstream 或 fstream 对象,而不是 cin 对象。
文件位置指针
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 异常处理
异常是程序在执行期间产生的问题。C 异常是指在程序运行时发生的特殊情况,比如尝试除以零的操作。
异常提供了一种转移程序控制权的方式。C 异常处理涉及到三个关键字:try、catch、throw。
throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。catch: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常。try: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch 块。
如果 try 块在不同的情境下会抛出不同的异常,这个时候可以尝试罗列多个 catch 语句,用于捕获不同类型的异常。
如果您想让 catch 块能够处理 try 块抛出的任何类型的异常,则必须在异常声明的括号内使用省略号 …
下面是一个实例,抛出一个除以零的异常,并在 catch 块中捕获该异常。
#include<iostream>usingnamespacestd;doubledivision(inta,intb){if(b==0){throw”Divisionbyzerocondition!”;}return(a/b);}intmain(){intx=50;inty=0;doublez=0;try{z=division(x,y);cout<<z<<endl;}catch(constchar*msg){cerr<<msg<<endl;}return0;}
由于我们抛出了一个类型为 const char* 的异常,因此,当捕获该异常时,我们必须在 catch 块中使用 const char*。当上面的代码被编译和执行时,它会产生下列结果:
Divisionbyzerocondition!C 标准的异常
C 提供了一系列标准的异常,定义在<exception> 中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的,如下所示:
C 异常的层次结构
下表是对上面层次结构中出现的每个异常的说明:
异常描述std::exception该异常是所有标准 C 异常的父类。std::bad_alloc该异常可以通过 new 抛出。std::bad_cast该异常可以通过 dynamic_cast 抛出。std::bad_exception这在处理 C 程序中无法预期的异常时非常有用。std::bad_typeid该异常可以通过 typeid 抛出。std::logic_error理论上可以通过读取代码来检测到的异常。std::domain_error当使用了一个无效的数学域时,会抛出该异常。std::invalid_argument当使用了无效的参数时,会抛出该异常。std::length_error当创建了太长的 std::string 时,会抛出该异常。std::out_of_range该异常可以通过方法抛出,例如 std::vector 和 std::bitset<>::operator。std::runtime_error理论上不可以通过读取代码来检测到的异常。std::overflow_error当发生数学上溢时,会抛出该异常。std::range_error当尝试存储超出范围的值时,会抛出该异常。std::underflow_error当发生数学下溢时,会抛出该异常。定义新的异常
您可以通过继承和重载 exception 类来定义新的异常。下面的实例演示了如何使用 std::exception 类来实现自己的异常:
#include<iostream>#include<exception>usingnamespacestd;structMyException:publicexception{constchar*what()constthrow(){return”C Exception”;}};intmain(){try{throwMyException();}catch(MyException&e){std::cout<<“MyExceptioncaught”<<std::endl;std::cout<<e.what()<<std::endl;}catch(std::exception&e){//其他的错误}}
这将产生以下结果:
MyExceptioncaughtC Exception
在这里,what() 是异常类提供的一个公共方法,它已被所有子异常类重载。这将返回异常产生的原因。
C 动态内存
C 程序中的内存分为两个部分:
**栈:**在函数内部声明的所有变量都将占用栈内存。**堆:**这是程序中未使用的内存,在程序运行时可用于动态分配内存。
很多时候,您无法提前预知需要多少内存来存储某个定义变量中的特定信息,所需内存的大小需要在运行时才能确定。
如果您不需要动态分配内存,可以使用 delete 运算符,删除之前由 new 运算符分配的内存。
new 和 delete 运算符
下面是使用 new 运算符来为任意的数据类型动态分配内存的通用语法:
newdata-type;
在这里,data-type 可以是包括数组在内的任意内置的数据类型,也可以是包括类或结构在内的用户自定义的任何数据类型。
例如,我们可以定义一个指向 double 类型的指针,然后请求内存,该内存在执行时被分配。我们可以按照下面的语句使用 new 运算符来完成这点:
double*pvalue=NULL;//初始化为null的指针pvalue=newdouble;//为变量请求内存
如果自由存储区已被用完,可能无法成功分配内存。所以建议检查 new 运算符是否返回 NULL 指针,并采取以下适当的操作:
double*pvalue=NULL;if(!(pvalue=newdouble)){cout<<“Error:outofmemory.”<<endl;exit(1);}
malloc() 函数在 C 语言中就出现了,在 C 中仍然存在,但建议尽量不要使用 malloc() 函数。new 与 malloc() 函数相比,其主要的优点是,new 不只是分配了内存,它还创建了对象。
在任何时候,当您觉得某个已经动态分配内存的变量不再需要使用时,您可以使用 delete 操作符释放它所占用的内存,如下所示:
deletepvalue;//释放pvalue所指向的内存
下面的实例中使用了上面的概念,演示了如何使用 new 和 delete 运算符:
当上面的代码被编译和执行时,它会产生下列结果:
Valueofpvalue:29495数组的动态内存分配
假设我们要为一个字符数组(一个有 20 个字符的字符串)分配内存,我们可以使用上面实例中的语法来为数组动态地分配内存,如下所示:
char*pvalue=NULL;//初始化为null的指针pvalue=newchar[20];//为变量请求内存
要删除我们刚才创建的数组,语句如下:
delete[]pvalue;//删除pvalue所指向的数组
下面是 new 操作符的通用语法,可以为多维数组分配内存,如下所示:
intROW=2;intCOL=3;double**pvalue=newdouble*[ROW];//为行分配内存//为列分配内存for(inti=0;i<COL;i ){pvalue[i]=newdouble[COL];}
释放多维数组内存:
for(inti=0;i<COL;i ){delete[]pvalue[i];}delete[]pvalue;对象的动态内存分配
对象与简单的数据类型没有什么不同。例如,请看下面的代码,我们将使用一个对象数组来理清这一概念:
#include<iostream>usingnamespacestd;classBox{public:Box(){cout<<“调用构造函数!”<<endl;}~Box(){cout<<“调用析构函数!”<<endl;}};intmain(){Box*myBoxArray=newBox[4];delete[]myBoxArray;//Deletearrayreturn0;}
如果要为一个包含四个 Box 对象的数组分配内存,构造函数将被调用 4 次,同样地,当删除这些对象时,析构函数也将被调用相同的次数(4次)。
当上面的代码被编译和执行时,它会产生下列结果:
调用构造函数!调用构造函数!调用构造函数!调用构造函数!调用析构函数!调用析构函数!调用析构函数!调用析构函数!C 命名空间
假设这样一种情况,当一个班上有两个名叫 Zara 的学生时,为了明确区分它们,我们在使用名字之外,不得不使用一些额外的信息,比如他们的家庭住址,或者他们父母的名字等等。
同样的情况也出现在 C 应用程序中。例如,您可能会写一个名为 xyz() 的函数,在另一个可用的库中也存在一个相同的函数 xyz()。这样,编译器就无法判断您所使用的是哪一个 xyz() 函数。
因此,引入了命名空间这个概念,专门用于解决上面的问题,它可作为附加信息来区分不同库中相同名称的函数、类、变量等。使用了命名空间即定义了上下文。本质上,命名空间就是定义了一个范围。
定义命名空间
命名空间的定义使用关键字 namespace,后跟命名空间的名称,如下所示:
namespacenamespace_name{//代码声明}
为了调用带有命名空间的函数或变量,需要在前面加上命名空间的名称,如下所示:
name::code;//code可以是变量或函数
让我们来看看命名空间如何为变量或函数等实体定义范围:
#include<iostream>usingnamespacestd;//第一个命名空间namespacefirst_space{voidfunc(){cout<<“Insidefirst_space”<<endl;}}//第二个命名空间namespacesecond_space{voidfunc(){cout<<“Insidesecond_space”<<endl;}}intmain(){//调用第一个命名空间中的函数first_space::func();//调用第二个命名空间中的函数second_space::func();return0;}
当上面的代码被编译和执行时,它会产生下列结果:
Insidefirst_spaceInsidesecond_spaceusing 指令
您可以使用 using namespace 指令,这样在使用命名空间时就可以不用在前面加上命名空间的名称。这个指令会告诉编译器,后续的代码将使用指定的命名空间中的名称。
#include<iostream>usingnamespacestd;//第一个命名空间namespacefirst_space{voidfunc(){cout<<“Insidefirst_space”<<endl;}}//第二个命名空间namespacesecond_space{voidfunc(){cout<<“Insidesecond_space”<<endl;}}usingnamespacefirst_space;intmain(){//调用第一个命名空间中的函数func();return0;}
当上面的代码被编译和执行时,它会产生下列结果:
Inside first_space
using 指令也可以用来指定命名空间中的特定项目。例如,如果您只打算使用 std 命名空间中的 cout 部分,您可以使用如下的语句:
using std::cout;
随后的代码中,在使用 cout 时就可以不用加上命名空间名称作为前缀,但是 std 命名空间中的其他项目仍然需要加上命名空间名称作为前缀,如下所示:
#include<iostream>usingstd::cout;intmain(){cout<<“std::endlisusedwithstd!”<<std::endl;return0;}
当上面的代码被编译和执行时,它会产生下列结果:
std::endl is used with std!
using 指令引入的名称遵循正常的范围规则。名称从使用 using 指令开始是可见的,直到该范围结束。此时,在范围以外定义的同名实体是隐藏的。
不连续的命名空间
命名空间可以定义在几个不同的部分中,因此命名空间是由几个单独定义的部分组成的。
一个命名空间的各个组成部分可以分散在多个文件中。
所以,如果命名空间中的某个组成部分需要请求定义在另一个文件中的名称,则仍然需要声明该名称。
下面的命名空间定义可以是定义一个新的命名空间,也可以是为已有的命名空间增加新的元素:
namespacenamespace_name{//代码声明}嵌套的命名空间
命名空间可以嵌套,您可以在一个命名空间中定义另一个命名空间,如下所示:
namespacenamespace_name1{//代码声明namespacenamespace_name2{//代码声明}}
您可以通过使用 :: 运算符来访问嵌套的命名空间中的成员:
//访问namespace_name2中的成员usingnamespacenamespace_name1::namespace_name2;//访问namespace:name1中的成员usingnamespacenamespace_name1;
在上面的语句中,如果使用的是 namespace_name1,那么在该范围内 namespace_name2 中的元素也是可用的,如下所示:
#include<iostream>usingnamespacestd;//第一个命名空间namespacefirst_space{voidfunc(){cout<<“Insidefirst_space”<<endl;}//第二个命名空间namespacesecond_space{voidfunc(){cout<<“Insidesecond_space”<<endl;}}}usingnamespacefirst_space::second_space;intmain(){//调用第二个命名空间中的函数func();return0;}
当上面的代码被编译和执行时,它会产生下列结果:
Inside second_spaceC 模板
模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。
模板是创建泛型类或函数的蓝图或公式。库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。
每个容器都有一个单一的定义,比如 向量,我们可以定义许多不同类型的向量,比如 vector <int> 或 vector <string>。
您可以使用模板来定义函数和类,接下来让我们一起来看看如何使用。
函数模板
模板函数定义的一般形式如下所示:
template<classtype>ret-typefunc-name(parameterlist){//函数的主体}
在这里,type 是函数所使用的数据类型的占位符名称。这个名称可以在函数定义中使用。
下面是函数模板的实例,返回两个数中的最大值:
#include<iostream>#include<string>usingnamespacestd;template<typenameT>inlineTconst&Max(Tconst&a,Tconst&b){returna<b?b:a;}intmain(){inti=39;intj=20;cout<<“Max(i,j):”<<Max(i,j)<<endl;doublef1=13.5;doublef2=20.7;cout<<“Max(f1,f2):”<<Max(f1,f2)<<endl;strings1=”Hello”;strings2=”World”;cout<<“Max(s1,s2):”<<Max(s1,s2)<<endl;return0;}
当上面的代码被编译和执行时,它会产生下列结果:
Max(i, j): 39Max(f1, f2): 20.7Max(s1, s2): World类模板
正如我们定义函数模板一样,我们也可以定义类模板。泛型类声明的一般形式如下所示:
template<classtype>classclass-name{}
在这里,type 是占位符类型名称,可以在类被实例化的时候进行指定。您可以使用一个逗号分隔的列表来定义多个泛型数据类型。
下面的实例定义了类 Stack<>,并实现了泛型方法来对元素进行入栈出栈操作:
#include<iostream>#include<vector>#include<cstdlib>#include<string>#include<stdexcept>usingnamespacestd;template<classT>classStack{private:vector<T>elems;//元素public:voidpush(Tconst&);//入栈voidpop();//出栈Ttop()const;//返回栈顶元素boolempty()const{//如果为空则返回真。returnelems.empty();}};template<classT>voidStack<T>::push(Tconst&elem){//追加传入元素的副本elems.push_back(elem);}template<classT>voidStack<T>::pop(){if(elems.empty()){throwout_of_range(“Stack<>::pop():emptystack”);}//删除最后一个元素elems.pop_back();}template<classT>TStack<T>::top()const{if(elems.empty()){throwout_of_range(“Stack<>::top():emptystack”);}//返回最后一个元素的副本returnelems.back();}intmain(){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(exceptionconst&ex){cerr<<“Exception:”<<ex.what()<<endl;return-1;}}
当上面的代码被编译和执行时,它会产生下列结果:
7helloException: Stack<>::pop(): empty stackC 预处理器
预处理器是一些指令,指示编译器在实际编译之前所需完成的预处理。
所有的预处理器指令都是以井号(#)开头,只有空格字符可以出现在预处理指令之前。
预处理指令不是 C 语句,所以它们不会以分号(;)结尾。
我们已经看到,之前所有的实例中都有 #include 指令。这个宏用于把头文件包含到源文件中。
C 还支持很多预处理指令,比如 #include、#define、#if、#else、#line 等,让我们一起看看这些重要指令。
#define 预处理
#define 预处理指令用于创建符号常量。该符号常量通常称为宏,指令的一般形式是:
#definemacro-namereplacement-text
当这一行代码出现在一个文件中时,在该文件中后续出现的所有宏都将会在程序编译之前被替换为 replacement-text。例如:
#include<iostream>usingnamespacestd;#definePI3.14159intmain(){cout<<“ValueofPI:”<<PI<<endl;return0;}
现在,让我们测试这段代码,看看预处理的结果。假设源代码文件已经存在,接下来使用 -E 选项进行编译,并把结果重定向到 test.p。
$gcc -E test.cpp > test.p
现在,如果您查看 test.p 文件,将会看到它已经包含大量的信息,而且在文件底部的值被改为如下:
intmain(){cout<<“ValueofPI:”<<3.14159<<endl;return0;}函数宏
您可以使用 #define 来定义一个带有参数的宏,如下所示:
#include<iostream>usingnamespacestd;#defineMIN(a,b)((a<b)?a:b)intmain(){inti,j;i=100;j=30;cout<<“Theminimumis”<<MIN(i,j)<<endl;return0;}
当上面的代码被编译和执行时,它会产生下列结果:
The minimum is 30条件编译
有几个指令可以用来有选择地对部分程序源代码进行编译。这个过程被称为条件编译。
条件预处理器的结构与 if 选择结构很像。请看下面这段预处理器的代码:
#ifndefNULL#defineNULL0#endif
您可以只在调试时进行编译,调试开关可以使用一个宏来实现,如下所示:
#ifdefDEBUGcerr<<“Variablex=”<<x<<endl;#endif
如果在指令 #ifdef DEBUG 之前已经定义了符号常量 DEBUG,则会对程序中的 cerr 语句进行编译。您可以使用 #if 0 语句注释掉程序的一部分,如下所示:
#if0不进行编译的代码#endif
让我们尝试下面的实例:
#include<iostream>usingnamespacestd;#defineDEBUG#defineMIN(a,b)(((a)<(b))?a:b)intmain(){inti,j;i=100;j=30;#ifdefDEBUGcerr<<“Trace:Insidemainfunction”<<endl;#endif#if0/*这是注释部分*/cout<<MKSTR(HELLOC )<<endl;#endifcout<<“Theminimumis”<<MIN(i,j)<<endl;#ifdefDEBUGcerr<<“Trace:Comingoutofmainfunction”<<endl;#endifreturn0;}
当上面的代码被编译和执行时,它会产生下列结果:
Trace: Inside main functionThe minimum is 30Trace: Coming out of main function# 和 ## 运算符
# 和 ## 预处理运算符在 C 和 ANSI/ISO C 中都是可用的。
#运算符会把 replacement-text 令牌转换为用引号引起来的字符串。
请看下面的宏定义:
#include<iostream>usingnamespacestd;#defineMKSTR(x)#xintmain(){cout<<MKSTR(HELLOC )<<endl;return0;}
当上面的代码被编译和执行时,它会产生下列结果:
HELLO C
让我们来看看它是如何工作的。不难理解,C 预处理器把下面这行:
cout<<MKSTR(HELLOC )<<endl;
转换成了:
cout<<“HELLOC “<<endl;
## 运算符用于连接两个令牌。下面是一个实例:
#defineCONCAT(x,y)x##y
当 CONCAT 出现在程序中时,它的参数会被连接起来,并用来取代宏。例如,程序中 CONCAT(HELLO, C ) 会被替换为 “HELLO C “,如下面实例所示。
#include<iostream>usingnamespacestd;#defineconcat(a,b)a##bintmain(){intxy=100;cout<<concat(x,y);return0;}
当上面的代码被编译和执行时,它会产生下列结果:
100
让我们来看看它是如何工作的。不难理解,C 预处理器把下面这行:
cout<<concat(x,y);
转换成了:
cout<<xy;C 中的预定义宏
C 提供了下表所示的一些预定义宏:
宏描述_LINE_这会在程序编译时包含当前行号。_FILE_这会在程序编译时包含当前文件名。_DATE_这会包含一个形式为 month/day/year 的字符串,它表示把源文件转换为目标代码的日期。_TIME_这会包含一个形式为 hour:minute:second 的字符串,它表示程序被编译的时间。
让我们看看上述这些宏的实例:
#include<iostream>usingnamespacestd;intmain(){cout<<“Valueof__LINE__:”<<__LINE__<<endl;cout<<“Valueof__FILE__:”<<__FILE__<<endl;cout<<“Valueof__DATE__:”<<__DATE__<<endl;cout<<“Valueof__TIME__:”<<__TIME__<<endl;return0;}
当上面的代码被编译和执行时,它会产生下列结果:
Value of __LINE__ : 6Value of __FILE__ : test.cppValue of __DATE__ : Feb 28 2011Value of __TIME__ : 18:52:48C 信号处理
信号是由操作系统传给进程的中断,会提早终止一个程序。在 UNIX、LINUX、Mac OS X 或 Windows 系统上,可以通过按 Ctrl C 产生中断。
有些信号不能被程序捕获,但是下表所列信号可以在程序中捕获,并可以基于信号采取适当的动作。这些信号是定义在 C 头文件 <csignal> 中。
信号描述SIGABRT程序的异常终止,如调用 abort。SIGFPE错误的算术运算,比如除以零或导致溢出的操作。SIGILL检测非法指令。SIGINT接收到交互注意信号。SIGSEGV非法访问内存。SIGTERM发送到程序的终止请求。signal() 函数
C 信号处理库提供了 signal 函数,用来捕获突发事件。以下是 signal() 函数的语法:
void(*signal(intsig,void(*func)(int)))(int);
这个函数接收两个参数:第一个参数是一个整数,代表了信号的编号;第二个参数是一个指向信号处理函数的指针。
让我们编写一个简单的 C 程序,使用 signal() 函数捕获 SIGINT 信号。不管您想在程序中捕获什么信号,您都必须使用 signal 函数来注册信号,并将其与信号处理程序相关联。看看下面的实例:
#include<iostream>#include<csignal>#include<unistd.h>usingnamespacestd;voidsignalHandler(intsignum){cout<<“Interruptsignal(“<<signum<<“)received.\n”;//清理并关闭//终止程序exit(signum);}intmain(){//注册信号SIGINT和信号处理程序signal(SIGINT,signalHandler);while(1){cout<<“Goingtosleep….”<<endl;sleep(1);}return0;}
当上面的代码被编译和执行时,它会产生下列结果:
Going to sleep….Going to sleep….Going to sleep….
现在,按 Ctrl C 来中断程序,您会看到程序捕获信号,程序打印如下内容并退出:
Going to sleep….Going to sleep….Going to sleep….Interrupt signal (2) received.raise() 函数
您可以使用函数 raise() 生成信号,该函数带有一个整数信号编号作为参数,语法如下:
intraise(signalsig);
在这里,sig 是要发送的信号的编号,这些信号包括:SIGINT、SIGABRT、SIGFPE、SIGILL、SIGSEGV、SIGTERM、SIGHUP。以下是我们使用 raise() 函数内部生成信号的实例:
#include<iostream>#include<csignal>usingnamespacestd;voidsignalHandler(intsignum){cout<<“Interruptsignal(“<<signum<<“)received.\n”;//清理并关闭//终止程序exit(signum);}intmain(){inti=0;//注册信号SIGINT和信号处理程序signal(SIGINT,signalHandler);while( i){cout<<“Goingtosleep….”<<endl;if(i==3){raise(SIGINT);}sleep(1);}return0;}
当上面的代码被编译和执行时,它会产生下列结果,并会自动退出:
Going to sleep….Going to sleep….Going to sleep….Interrupt signal (2) received.C 多线程
多线程是多任务处理的一种特殊形式,多任务处理允许让电脑同时运行两个或两个以上的程序。在一般情况下,有两种类型的多任务处理:基于进程和基于线程。
基于进程的多任务处理处理的是程序的并发执行。基于线程的多任务处理的是同一程序的片段的并发执行。
多线程程序包含可以同时运行的两个或多个部分。这样的程序中的每个部分称为一个线程,每个线程定义了一个单独的执行路径。
C 不包含多线程应用程序的任何内置支持。相反,它完全依赖于操作系统来提供此功能。
创建线程
有下面的例程,我们可以用它来创建一个 POSIX 线程:
#include<pthread.h>pthread_create(thread,attr,start_routine,arg)
在这里,pthread_create 创建一个新的线程,并让它可执行。这个例程可在代码内的任何地方被调用任意次数。下面是关于参数的说明:
参数描述thread一个不透明的、唯一的标识符,用来标识例程返回的新线程。attr一个不透明的属性对象,可以被用来设置线程属性。您可以指定线程属性对象,也可以使用默认值 NULL。start_routineC 例程,一旦线程被创建就会执行。arg一个可能传递给 start_routine 的参数。它必须通过把引用作为指针强制转换为 void 类型进行传递。如果没有传递参数,则使用 NULL。
一个进程可以创建的最大线程数是依赖于实现的。线程一旦被创建,就是同等的,而且可以创建其他线程。线程之间没有隐含层次或依赖。
终止线程
有下面的例程,我们可以用它来终止一个 POSIX 线程:
#include<pthread.h>pthread_exit(status)
在这里,pthread_exit 用于显式地退出一个线程。通常情况下,pthread_exit() 例程是在线程完成工作后无需继续存在时被调用。
如果 main() 是在它所创建的线程之前结束,并通过 pthread_exit() 退出,那么其他线程将继续执行。否则,它们将在 main() 结束时自动被终止。
实例
这个简单的实例代码使用 pthread_create() 例程创建了 5 个线程。每个线程打印一个 “Hello World!” 消息,然后调用 pthread_exit() 终止线程。
#include<iostream>//必须的头文件是#include<pthread.h>usingnamespacestd;#defineNUM_THREADS5//线程的运行函数void*say_hello(void*args){cout<<“Hello w3cschool!”<<endl;}intmain(){//定义线程的id变量,多个变量使用数组pthread_ttids[NUM_THREADS];for(inti=0;i<NUM_THREADS; i){//参数依次是:创建的线程id,线程参数,调用的函数,传入的函数参数intret=pthread_create(&tids[i],NULL,say_hello,NULL);if(ret!=0){cout<<“pthread_createerror:error_code=”<<ret<<endl;}}//等各个线程退出后,进程才结束,否则进程强制结束了,线程可能还没反应过来;pthread_exit(NULL);}
使用 -lpthread 库编译下面的程序:
$g test.cpp-lpthread-otest.o
现在,执行程序,将产生下列结果:
$ ./test.oHello w3cschool!Hello w3cschool!Hello w3cschool!Hello w3cschool!Hello w3cschool!
以下简单的实例代码使用 pthread_create() 函数创建了 5 个线程,并接收传入的参数。每个线程打印一个 “Hello w3cschool!” 消息,并输出接收的参数,然后调用 pthread_exit() 终止线程。
//文件名:test.cpp#include<iostream>#include<cstdlib>#include<pthread.h>usingnamespacestd;#defineNUM_THREADS5void*PrintHello(void*threadid){//对传入的参数进行强制类型转换,由无类型指针变为整形数指针,然后再读取inttid=*((int*)threadid);cout<<“Hello w3cschool!线程 ID, “<<tid<<endl;pthread_exit(NULL);}intmain(){pthread_tthreads[NUM_THREADS];intindexes[NUM_THREADS];//用数组来保存i的值intrc;inti;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.omain() : 创建线程, 0main() : 创建线程, 1main() : 创建线程, 2main() : 创建线程, 3main() : 创建线程, 4Hello w3cschool! 线程 ID, 4Hello w3cschool! 线程 ID, 3Hello w3cschool! 线程 ID, 2Hello w3cschool! 线程 ID, 1Hello w3cschool! 线程 ID, 0
向线程传递参数
这个实例演示了如何通过结构传递多个参数。您可以在线程回调中传递任意的数据类型,因为它指向 void,如下面的实例所示:
#include<iostream>#include<cstdlib>#include<pthread.h>usingnamespacestd;#defineNUM_THREADS5structthread_data{intthread_id;char*message;};void*PrintHello(void*threadarg){structthread_data*my_data;my_data=(structthread_data*)threadarg;cout<<“ThreadID:”<<my_data->thread_id;cout<<“Message:”<<my_data->message<<endl;pthread_exit(NULL);}intmain(){pthread_tthreads[NUM_THREADS];structthread_datatd[NUM_THREADS];intrc;inti;for(i=0;i<NUM_THREADS;i ){cout<<“main():creatingthread,”<<i<<endl;td[i].thread_id=i;td[i].message=”Thisismessage”;rc=pthread_create(&threads[i],NULL,PrintHello,(void*)&td[i]);if(rc){cout<<“Error:unabletocreatethread,”<<rc<<endl;exit(-1);}}pthread_exit(NULL);}
当上面的代码被编译和执行时,它会产生下列结果:
$ g -Wno-write-strings test.cpp -lpthread -o test.o$ ./test.omain() : creating thread, 0main() : creating thread, 1main() : creating thread, 2main() : creating thread, 3main() : creating thread, 4Thread ID : 3 Message : This is messageThread ID : 2 Message : This is messageThread ID : 0 Message : This is messageThread ID : 1 Message : This is messageThread ID : 4 Message : This is message连接和分离线程
有以下两个例程,我们可以用它们来连接或分离线程:
pthread_join(threadid,status)pthread_detach(threadid)
pthread_join() 子例程阻碍调用例程,直到指定的 threadid 线程终止为止。当创建一个线程时,它的某个属性会定义它是否是可连接的(joinable)或可分离的(detached)。只有创建时定义为可连接的线程才可以被连接。如果线程创建时被定义为可分离的,则它永远也不能被连接。
这个实例演示了如何使用 pthread_join() 例程来等待线程的完成。
#include<iostream>#include<cstdlib>#include<pthread.h>#include<unistd.h>usingnamespacestd;#defineNUM_THREADS5void*wait(void*t){inti;longtid;tid=(long)t;sleep(1);cout<<“Sleepinginthread”<<endl;cout<<“Threadwithid:”<<tid<<“…exiting”<<endl;pthread_exit(NULL);}intmain(){intrc;inti;pthread_tthreads[NUM_THREADS];pthread_attr_tattr;void*status;//初始化并设置线程为可连接的(joinable)pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE);for(i=0;i<NUM_THREADS;i ){cout<<“main():creatingthread,”<<i<<endl;rc=pthread_create(&threads[i],NULL,wait,(void*)i);if(rc){cout<<“Error:unabletocreatethread,”<<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:unabletojoin,”<<rc<<endl;exit(-1);}cout<<“Main:completedthreadid:”<<i;cout<<“exitingwithstatus:”<<status<<endl;}cout<<“Main:programexiting.”<<endl;pthread_exit(NULL);}
当上面的代码被编译和执行时,它会产生下列结果:
main() : creating thread, 0main() : creating thread, 1main() : creating thread, 2main() : creating thread, 3main() : creating thread, 4Sleeping in threadThread with id : 4 …exitingSleeping in threadThread with id : 3 …exitingSleeping in threadThread with id : 2 …exitingSleeping in threadThread with id : 1 …exitingSleeping in threadThread with id : 0 …exitingMain: completed thread id :0 exiting with status :0Main: completed thread id :1 exiting with status :0Main: completed thread id :2 exiting with status :0Main: completed thread id :3 exiting with status :0Main: completed thread id :4 exiting with status :0Main: program exiting.C Web 编程什么是 CGI?公共网关接口(CGI),是一套标准,定义了信息是如何在 Web 服务器和客户端脚本之间进行交换的。CGI 规范目前是由 NCSA 维护的,NCSA 定义 CGI 如下:公共网关接口(CGI),是一种用于外部网关程序与信息服务器(如 HTTP 服务器)对接的接口标准。目前的版本是 CGI/1.1,CGI/1.2 版本正在推进中。Web 浏览
您的浏览器联系上 HTTP Web 服务器,并请求 URL,即文件名。Web 服务器将解析 URL,并查找文件名。如果找到请求的文件,Web 服务器会把文件发送回浏览器,否则发送一条错误消息,表明您请求了一个错误的文件。Web 浏览器从 Web 服务器获取响应,并根据接收到的响应来显示文件或错误消息。
然而,以这种方式搭建起来的 HTTP 服务器,不管何时请求目录中的某个文件,HTTP 服务器发送回来的不是该文件,而是以程序形式执行,并把执行产生的输出发送回浏览器显示出来。
公共网关接口(CGI),是使得应用程序(称为 CGI 程序或 CGI 脚本)能够与 Web 服务器以及客户端进行交互的标准协议。这些 CGI 程序可以用 Python、PERL、Shell、C 或 C 等进行编写。
CGI 架构图
下图演示了 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”>AllowOverrideNoneOptionsExecCGIOrderallow,denyAllowfromall</Directory><Directory”/var/www/cgi-bin”>OptionsAll</Directory>
在这里,我们假设已经配置好 Web 服务器并能成功运行,你可以运行任意的 CGI 程序,比如 Perl 或 Shell 等。
第一个 CGI 程序
请看下面的 C 程序:
#include<iostream>usingnamespacestd;intmain(){cout<<“Content-type:text/html\r\n\r\n”;cout<<“<html>\n”;cout<<“<head>\n”;cout<<“<title>HelloWorld-第一个CGI程序</title>\n”;cout<<“</head>\n”;cout<<“<body>\n”;cout<<“<h2>HelloWorld!这是我的第一个CGI程序</h2>\n”;cout<<“</body>\n”;cout<<“</html>\n”;return0;}
编译上面的代码,把可执行文件命名为 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)进行交互。
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 程序时扮演了非常重要的角色。
下面的 CGI 程序列出了所有的 CGI 变量。
#include<iostream>#include<stdlib.h>usingnamespacestd;conststringENV[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”};intmain(){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<<“<tableborder=\”0\”cellspacing=\”2\”>”;for(inti=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”;return0;}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
GET 和 POST 方法
您可能有遇到过这样的情况,当您需要从浏览器传递一些信息到 Web 服务器,最后再传到 CGI 程序。通常浏览器会使用两种方法把这个信息传到 Web 服务器,分别是 GET 和 POST 方法。
使用 GET 方法传递信息
GET 方法发送已编码的用户信息追加到页面请求中。页面和已编码信息通过 ? 字符分隔开,如下所示:
http://www.test.com/cgi-bin/cpp.cgi?key1=value1&key2=value2
当使用 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>usingnamespacestd;usingnamespacecgicc;intmain(){CgiccformData;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_iteratorfi=formData.getElement(“first_name”);if(!fi->isEmpty()&&fi!=(*formData).end()){cout<<“名:”<<**fi<<endl;}else{cout<<“Notextenteredforfirstname”<<endl;}cout<<“<br/>\n”;fi=formData.getElement(“last_name”);if(!fi->isEmpty()&&fi!=(*formData).end()){cout<<“姓:”<<**fi<<endl;}else{cout<<“Notextenteredforlastname”<<endl;}cout<<“<br/>\n”;cout<<“</body>\n”;cout<<“</html>\n”;return0;}
现在,编译上面的程序,如下所示:
$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简单的表单实例: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 方法,如下所示:
<formaction=”/cgi-bin/cpp_get.cgi”method=”post”>名:<input type=”text”name=”first_name”><br/>姓:<input type=”text”name=”last_name”/><inputtype=”submit”value=”提交”/></form>向 CGI 程序传递复选框数据
当需要选择多个选项时,我们使用复选框。
下面的 HTML 代码实例是一个带有两个复选框的表单:
<formaction=”/cgi-bin/cpp_checkbox.cgi”method=”POST”target=”_blank”><inputtype=”checkbox”name=”maths”value=”on”/>数学<inputtype=”checkbox”name=”physics”value=”on”/>物理<inputtype=”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>usingnamespacestd;usingnamespacecgicc;intmain(){CgiccformData;boolmaths_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<<“MathsFlag:ON”<<endl;}else{cout<<“MathsFlag:OFF”<<endl;}cout<<“<br/>\n”;physics_flag=formData.queryCheckbox(“physics”);if(physics_flag){cout<<“PhysicsFlag:ON”<<endl;}else{cout<<“PhysicsFlag:OFF”<<endl;}cout<<“<br/>\n”;cout<<“</body>\n”;cout<<“</html>\n”;return0;}向 CGI 程序传递单选按钮数据
当只需要选择一个选项时,我们使用单选按钮。
下面的 HTML 代码实例是一个带有两个单选按钮的表单:
<formaction=”/cgi-bin/cpp_radiobutton.cgi”method=”post”target=”_blank”><inputtype=”radio”name=”subject”value=”maths”checked=”checked”/>数学<inputtype=”radio”name=”subject”value=”physics”/>物理<inputtype=”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>usingnamespacestd;usingnamespacecgicc;intmain(){CgiccformData;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_iteratorfi=formData.getElement(“subject”);if(!fi->isEmpty()&&fi!=(*formData).end()){cout<<“Radioboxselected:”<<**fi<<endl;}cout<<“<br/>\n”;cout<<“</body>\n”;cout<<“</html>\n”;return0;}向 CGI 程序传递文本区域数据
当需要向 CGI 程序传递多行文本时,我们使用 TEXTAREA 元素。
下面的 HTML 代码实例是一个带有 TEXTAREA 框的表单:
<formaction=”/cgi-bin/cpp_textarea.cgi”method=”post”target=”_blank”><textareaname=”textcontent”cols=”40″rows=”4″>请在这里输入文本…</textarea><inputtype=”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>usingnamespacestd;usingnamespacecgicc;intmain(){CgiccformData;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_iteratorfi=formData.getElement(“textcontent”);if(!fi->isEmpty()&&fi!=(*formData).end()){cout<<“TextContent:”<<**fi<<endl;}else{cout<<“Notextentered”<<endl;}cout<<“<br/>\n”;cout<<“</body>\n”;cout<<“</html>\n”;return0;}向 CGI 程序传递下拉框数据
当有多个选项可用,但只能选择一个或两个选项时,我们使用下拉框。
下面的 HTML 代码实例是一个带有下拉框的表单:
<formaction=”/cgi-bin/cpp_dropdown.cgi”method=”post”target=”_blank”><selectname=”dropdown”><optionvalue=”Maths”selected>数学</option><optionvalue=”Physics”>物理</option></select><inputtype=”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>usingnamespacestd;usingnamespacecgicc;intmain(){CgiccformData;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_iteratorfi=formData.getElement(“dropdown”);if(!fi->isEmpty()&&fi!=(*formData).end()){cout<<“ValueSelected:”<<**fi<<endl;}cout<<“<br/>\n”;cout<<“</body>\n”;cout<<“</html>\n”;return0;}在 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>usingnamespacestd;intmain(){cout<<“Set-Cookie:UserID=XYZ;\r\n”;cout<<“Set-Cookie:Password=XYZ123;\r\n”;cout<<“Set-Cookie:Domain=www.w3cschool.cn;\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”;return0;}
从这个实例中,我们了解了如何设置 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>usingnamespacestd;usingnamespacecgicc;intmain(){Cgicccgi;const_cookie_iteratorcci;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<<“<tableborder=\”0\”cellspacing=\”2\”>”;//获取环境变量constCgiEnvironment&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”;return0;}
现在,编译上面的程序,生成 getcookies.cgi,并尝试使用下面的链接获取您的计算机上所有可用的 cookies:
/cgi-bin/getcookies.cgi
这会产生一个列表,显示了上一节中设置的四个 cookies 以及您的计算机上所有其他的 cookies:
UserID XYZPassword XYZ123Domain www.w3cschool.cnPath /perl文件上传实例
为了上传一个文件,HTML 表单必须把 enctype 属性设置为 multipart/form-data。带有文件类型的 input 标签会创建一个 “Browse” 按钮。
<html><body><formenctype=”multipart/form-data”action=”/cgi-bin/cpp_uploadfile.cgi”method=”post”><p>文件:<inputtype=”file”name=”userfile”/></p><p><inputtype=”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>usingnamespacestd;usingnamespacecgicc;intmain(){Cgicccgi;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_iteratorfile=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”;return0;}
上面的实例是在 cout 流中写入内容,但您可以打开文件流,并把上传的文件内容保存在目标位置的某个文件中。