友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
  
  
Java编程思想第4版[中文版](PDF格式)-第98部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
确实不应把这些东西都塞到一个头文件里。但就目前的例子来说,这样做不会造成任何方面的损害,而且更 
具有Java 风格,所以大家阅读理解代码时要显得轻松一些:  
                                                                             566 
…………………………………………………………Page 568……………………………………………………………
  
//: CGITools。h  
// Automatically extracts and decodes data  
// from CGI GETs and POSTs。 Tested with GNU C++   
// (available for most server machines)。  
#include   
#include  // STL vector  
using namespace std;  
  
// A class to hold a single name…value pair from  
// a CGI query。 CGI_vector holds Pair objects and  
// returns them from its operator''。  
class Pair {  
  char* nm;  
  char* val;  
public:  
  Pair() { nm = val = 0; }  
  Pair(char* name; char* value) {  
    // Creates new memory:  
    nm = decodeURLString(name);  
    val = decodeURLString(value);  
  }  
  const char* name() const { return nm; }  
  const char* value() const { return val; }  
  // Test for 〃emptiness〃  
  bool empty() const {  
    return (nm == 0) || (val == 0);  
  }  
  // Automatic type conversion for boolean test:  
  operator bool() const {  
    return (nm != 0) && (val != 0);  
  }  
  // The following constructors & destructor are  
  // necessary for bookkeeping in C++。  
  // Copy…constructor:  
  Pair(const Pair& p) {  
    if(p。nm == 0 || p。val == 0) {  
      nm = val = 0;  
    } else {  
      // Create storage & copy rhs values:  
      nm = new char'strlen(p。nm) + 1';  
      strcpy(nm; p。nm);  
      val = new char'strlen(p。val) + 1';  
      strcpy(val; p。val);  
    }  
  }  
  // Assignment operator:  
  Pair& operator=(const Pair& p) {  
    // Clean up old lvalues:  
    delete nm;  
    delete val;  
    if(p。nm == 0 || p。val == 0) {  
                                                                                          567 
…………………………………………………………Page 569……………………………………………………………
      nm = val = 0;  
    } else {  
      // Create storage & copy rhs values:  
      nm = new char'strlen(p。nm) + 1';  
      strcpy(nm; p。nm);  
      val = new char'strlen(p。val) + 1';  
      strcpy(val; p。val);  
    }  
    return *this;  
  }   
  ~Pair() { // Destructor  
    delete nm; // 0 value OK  
    delete val;  
  }  
  // If you use this method outide this class;   
  // you're responsible for calling 'delete' on  
  // the pointer that's returned:  
  static char*   
  decodeURLString(const char* URLstr) {  
    int len = strlen(URLstr);  
    char* result = new char'len + 1';  
    memset(result; len + 1; 0);  
    for(int i = 0; j = 0; i = 'A')  
      return (hex & 0xdf) 'A' + 10;  
    else  
      return hex '0';  
  }  
};  
  
// Parses any CGI query and turns it  
// into an STL vector of Pair objects:  
class CGI_vector : public vector {  
  char* qry;  
  const char* start; // Save starting position  
  // Prevent assignment and copy…construction:  
  void operator=(CGI_vector&);  
                                                                                               568 
…………………………………………………………Page 570……………………………………………………………
  CGI_vector(CGI_vector&);  
public:  
  // const fields must be initialized in the C++  
  // 〃Constructor initializer list〃:  
  CGI_vector(char* query) :  
      start(new char'strlen(query) + 1') {  
    qry = (char*)start; // Cast to non…const  
    strcpy(qry; query);  
    Pair p;  
    while((p = nextPair()) != 0)  
      push_back(p);  
  }  
  // Destructor:  
  ~CGI_vector() { delete start; }  
private:  
  // Produces name…value pairs from the query   
  // string。 Returns an empty Pair when there's   
  // no more query string left:  
  Pair nextPair() {  
    char* name = qry;  
    if(name == 0 || *name == '0')  
      return Pair(); // End; return null Pair  
    char* value = strchr(name; '=');  
    if(value == 0)  
      return Pair(); // Error; return null Pair  
    // Null…terminate name; move value to start  
    // of its set of characters:  
    *value = '0';  
    value++;  
    // Look for end of value; marked by '&':  
    qry = strchr(value; '&');  
    if(qry == 0) qry = 〃〃; // Last pair found  
    else {  
      *qry = '0'; // Terminate value string  
      qry++; // Move to next pair  
    }  
    return Pair(name; value);  
  }  
}; ///:~  
  
在#include 语句后,可看到有一行是:  
using namespace std;  
C++中的“命名空间”(Namespace)解决了由Java 的package 负责的一个问题:将库名隐藏起来。std命名 
空间引用的是标准C++库,而 vector 就在这个库中,所以这一行是必需的。  
Pair 类表面看异常简单,只是容纳了两个(private)字符指针而已——一个用于名字,另一个用于值。默 
认构建器将这两个指针简单地设为零。这是由于在 C++中,对象的内存不会自动置零。第二个构建器调用方 
法 decodeURLString(),在新分配的堆内存中生成一个解码过后的字串。这个内存区域必须由对象负责管理 
及清除,这与“破坏器”中见到的相同。name()和 value()方法为相关的字段产生只读指针。利用 empty()方 
法,我们查询Pair 对象它的某个字段是否为空;返回的结果是一个 bool——C++内建的基本布尔数据类型。 
operator bool()使用的是 C++ “运算符过载”的一种特殊形式。它允许我们控制自动类型转换。如果有一个 
名为p 的Pair 对象,而且在一个本来希望是布尔结果的表达式中使用,比如 if(p){//。。。,那么编译器能辨 
别出它有一个Pair,而且需要的是个布尔值,所以自动调用 operator bool(),进行必要的转换。  
                                                                                         569 
…………………………………………………………Page 571……………………………………………………………
接下来的三个方法属于常规编码,在C++中创建类时必须用到它们。根据C++类采用的所谓“经典形式”,我 
们必须定义必要的“原始”构建器,以及一个副本构建器和赋值运算符——operator= (以及破坏器,用于清 
除内存)。之所以要作这样的定义,是由于编译器会“默默”地调用它们。在对象传入、传出一个函数的时 
候,需要调用副本构建器;而在分配对象时,需要调用赋值运算符。只有真正掌握了副本构建器和赋值运算 
符的工作原理,才能在 C++里写出真正“健壮”的类,但这需要需要一个比较艰苦的过程(注释⑤)。  
  
⑤:我的《Thinking in C++》(Prentice…Hall;1995)用了一整章的地方来讨论这个主题。若需更多的帮 
助,请务必看看那一章。  
  
只要将一个对象按值传入或传出函数,就会自动调用副本构建器Pair(const Pair&)。也就是说,对于准备 
为其制作一个完整副本的那个对象,我们不准备在函数框架中传递它的地址。这并不是Java 提供的一个选 
项,由于我们只能传递句柄,所以在Java 里没有所谓的副本构建器(如果想制作一个本地副本,可以“克 
隆”那个对象——使用 clone(),参见第12 章)。类似地,如果在 Java 里分配一个句柄,它会简单地复 
制。但 C++中的赋值意味着整个对象都会复制。在副本构建器中,我们创建新的存储空间,并复制原始数 
据。但对于赋值运算符,我们必须在分配新存储空间之前释放老存储空间。我们要见到的也许是 C++类最复 
杂的一种情况,但那正是Java 的支持者们论证Java 比C++简单得多的有力证据。在 Java 中,我们可以自由 
传递句柄,善后工作则由垃圾收集器负责,所以可以轻松许多。  
但事情并没有完。Pair 类为nm 和 val 使用的是char*,最复杂的情况主要是围绕指针展开的。如果用较时髦 
的C++ string 类来代替char*,事情就要变得简单得多(当然,并不是所有编译器都提供了对 string 的支 
持)。那么,Pair 的第一部分看起来就象下面这样:  
  
class Pair {  
  string nm;  
  string val;  
public:  
  Pair() { }  
  Pair(char* name; char* value) {  
    nm = decodeURLString(name);  
    val = decodeURLString(value);  
  }  
  const char* name() const { return nm。c_str(); }  
  const char* value() const {   
    return val。c_str();   
  }  
  // Test for 〃emptiness〃  
  bool empty() const {  
    return (nm。length() == 0)   
      || (val。length() == 0);  
  }  
  // Automatic type conversion for boolean test:  
  operator bool() const {  
    return (nm。length() != 0)   
      && (val。length() != 0);  
  }  
  
 (此外,对这个类decodeURLString()会返回一个 string,而不是一个char*)。我们不必定义副本构建 
器、operator=或者破坏器,因为编译器已帮我们做了,而且做得非常好。但即使有些事情是自动进行的, 
C++程序员也必须了解副本构建以及赋值的细节。  
Pair 类剩下的部分由两个方法构成:decodeURLString()以及一个“帮助器”方法translateHex()——将由 
decodeURLString()使用。注意 translateHex()并不能防范用户的恶意输入,比如“%1H”。分配好足够的存 
储空间后(必须由破坏器释放),decodeURLString()就会其中遍历,将所有“+”都换成一个空格;将所有 
十六进制代码(以一个“%”打头)换成对应的字符。  
                                                                                    570 
…………………………………………………………Page 572……………………………………………………………
CGI_vector 用于解析和容纳整个 CGI GET 命令。它是从 STL vector 里继承的,后者例示为容纳Pair 。C++中 
的继承是用一个冒号表示,在Java 中则要用extends。此外,继承默认为private 属性,所以几乎肯定需要 
用到public 关键字,就象这样做的那样。大家也会发现 CGI_vector 有一个副本构建器以及一个 
operator=,但它们都声明成private。这样做是为了防止编译器同步两个函数(如果不自己声明它们,两者 
就会同步)。但这同时也禁止了客户程序员按值或者通过赋值传递一个CGI_vector。  
CGI_vector 的工作是获取QUERY_STRING,并把它解析成“名称/值”对,这需要在Pair 的帮助下完成。它 
首先将字串复制到本地分配的内存,并用常数指针 start 跟踪起始地址(稍后会在破坏器中用于释放内 
存)。随后,它用自己的nextPair()方法将字串解析成原始的“名称/值”对,各个对之间用一个“=”和 
 “&”符号分隔。这些对由 nextPair()传递给 Pair 构建器,所以nextPair()返回的是一个Pair 对象。随后 
用push_back()将该对象加入vector。nextPair()遍历完整个 QUERY_STRING 后,会返回一个零值。  
现在基本工具已定义好,它们可以简单地在一个CGI 程序中使用,就象下面这样:  
  
//: Listmgr2。cpp  
// CGI version of Listmgr。c in C++; which   
// extracts its input via the GET submission   
// from the associated applet。 Also works as  
// an ordinary CGI program with HTML forms。  
#include   
#include 〃CGITools。h〃  
const char* dataFile = 〃list2。txt〃;  
const char* notify = 〃Bruce@EckelObjects。〃;  
#undef DEBUG  
  
// Similar code as before; except that it looks  
// for the email name inside of '':  
int inList(FILE* list; const char* emailName) {  
  const int BSIZE = 255;  
  char lbuf'BSIZE';  
  char emname'BSIZE';  
  // Put the email name in '' so there's no  
  // possibility of a match within another name:  
  sprintf(emname; 〃〃; emailName);  
  // Go to the beginning of the list:  
  fseek(list; 0; SEEK_SET);  
  // Read each line in the list:  
  while(fgets(lbuf; BSIZE; list)) {  
    // Strip off the newline:   
    char * newline = strchr(lbuf; 'n');  
    if(newline != 0)   
      *newline = '0';  
    if(strstr(lbuf; emname) != 0)  
      return 1;  
  }  
  return 0;  
}  
  
void main() {  
  // You MUST print this out; otherwise the   
  // server will not send the response:  
  printf(〃Content…type: text/plainnn〃);  
  FILE* list = fopen(dataFile; 〃a+t〃);  
  if(list == 0) {  
                                                                                             571 
…………………………………………………………Page 573……………………………………………………………
    printf(〃error: could not open database。 〃);  
    printf(〃Notify %s〃; notify);  
    return;  
  }  
  // For a CGI 〃GET;〃 the server puts the data  
  // in the environment variable QUERY_STRING:  
  CGI_vector query(getenv(〃QUERY_STRING〃));  
  #if defined(DEBUG)  
  // Test: dump all names and values  
  for(int i = 0; i 《 query。size(); i++) {  
    printf(〃query'%d'。name() = '%s'; 〃;   
      i; query'i'。name());  
    printf(〃query'%d'。value() = '%s' n〃;   
      i; query'i'。value());  
  }  
  #endif(DEBUG)  
  Pair name = query'0';  
  Pair email = query'1';  
  if(name。empty() || email。empty()) {  
    printf(〃error: null name or email〃);  
    return;  
  }   
  if(inList(list; email。value())) {  
    printf(〃Already in list: %s〃; email。value());  
    return;  
  }  
  // It's not in the list; add it:  
  fseek(list; 0; SEEK_END);  
  fprintf(list; 〃%s ;n〃;   
    name。value(); email。value());  
  fflush(list);  
  fclose(list);  
  printf(〃%s  added to listn〃;   
    name。value(); email。value());  
} ///:~  
  
alreadyInList()函数与前一个版本几乎是完全相同的,只是它假定所有电子函件地址都在一个“”内。  
在使用 GET 方法时(通过在FORM 引导命令的METHOD 标记内部设置,但这在这里由数据发送的方式控制), 
Web 服务器会收集位于“?”后面的所有信息,并把它们置入环境变量QUERY_STRING (查询字串)里。所以为 
了读取那些信息,必须获得QUERY_STRING 的值,这是用标准的C 库函数 getnv()完成的。在 main()中,注意 
对QUERY_STRING 的解析有多么容易:只需把它传递给用于CGI_vector 对象的构建器(名为query),剩下 
的所有工作都会自动进行。从这时开始,我们就可以从query 中取出名称和值,把它们当作数组看待(这是 
由于operator''在vector 里已经过载了)。在调试代码中,大家可看到这一切是如何运作的;调试代码封 
装在预处理器引导命令#if defined(DEBUG)和#endif(DEBUG) 之间。  
现在,我们迫切需要掌握一些与CGI 有关的东西。CGI 程序用两个方式之一传递它们的输入:在 GET 执行期 
间通过QUERY_STRING 传递(目前用的这种方式),或者在POST 期间通过标准输入。但 CGI 程序通过标准输 
出发送自己的输出,这通常是用C 程序的 printf() 命令实现的。那么这个输出到哪里去了呢?它回到了Web 
服务器,由服务器决定该如何处理它。服务器作出决定的依据是content…type (内容类型)头数据。这意味 
着假如 content…type 头不是它看到的第一件东西,就不知道该如何处理收到的数据。因此,我们无论如何也 
要使所有CGI 程序都从 content…type 头开始输出。  
在目前这种情况下,我们希望服务器将所有信息都直接反馈回客户程序(亦即我们的程序片,它们正在等候 
给自己的回复)。信息应该原封不动,所以content…type 设为text/plain (纯文本)。一旦服务器看到这 
                                                                                          572 
…………………………………………………………Page 574……………………………………………………………
个头,就会将所有字串都直接发还给客户。所以每个字串(三个用于出错条件,一个用于成功的加入)都会 
返回程序片。  
我们用相同的代码添加电子函件名称(用户的姓名)。但在CGI 脚本的情况下,并不存在无限循环——程序 
只是简单地响应,然后就中断。每次有一个CGI 请求抵达时,程序都会启动,对那个请求作出反应,然后自 
行关闭。所以CPU 不可能陷入空等待的尴尬境地,只有启动程序和打开文件时才存在性能上的隐患。Web 服 
务器对CGI 请求进行控制时,它的开销会将这种隐患减轻到最低程度。  
这种设计的另一个好处是由于  
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!