⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 apr-tutorial-19.html

📁 跨平台windowsunixlinux的c语言编程解决方案
💻 HTML
📖 第 1 页 / 共 2 页
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML><HEAD> <META NAME="GENERATOR" CONTENT="LinuxDoc-Tools 0.9.21"> <TITLE>libapr(apache portable runtime) programming tutorial: Container APIs</TITLE> <LINK HREF="apr-tutorial-20.html" REL=next> <LINK HREF="apr-tutorial-18.html" REL=previous> <LINK HREF="apr-tutorial.html#toc19" REL=contents></HEAD><BODY><A HREF="apr-tutorial-20.html">Next</A><A HREF="apr-tutorial-18.html">Previous</A><A HREF="apr-tutorial.html#toc19">Contents</A><HR><H2><A NAME="s19">19.</A> <A HREF="apr-tutorial.html#toc19">Container APIs</A></H2><P>libapr provides some container(a.k.a. collection) APIs.</P><H2><A NAME="ss19.1">19.1</A> <A HREF="apr-tutorial.html#toc19.1">dynamic array</A></H2><P>Array is the most general container type in C language. However, array size is noramally fixed. It's not flexible. libapr provides dynamic-sized array. The APIs are declared in apr_tables.h.</P><P>As container type, dynamic array has the following features.</P><P><BR><CENTER><TABLE BORDER><TR><TD>append</TD><TD>efficient(API support)</TD></TR><TR><TD>insert/prepend</TD><TD>inefficient(no API support)</TD></TR><TR><TD>delete</TD><TD>inefficient(no API support)</TD></TR><TR><TD>delete(only the first element)</TD><TD>efficient(API support)</TD></TR><TR><TD>search(lookup)</TD><TD>inefficient, but depends(no API support)</TD></TR><TR><TD>iteration</TD><TD>efficient(no API support)</TD></TR></TABLE><CAPTION>array</CAPTION></CENTER><BR></P><P>'No API support' above means that you have to touch apr_array_header_t::elts directly. This is not a good attitude as a progammer, but we should accept it as a pragmatic programmer.</P><P>The dynamic array type is apr_array_header_t. The object is created by apr_array_make() as follows:</P><P>/* excerpted from <A HREF="../sample/array-sample.c">array-sample.c</A> */<BLOCKQUOTE><CODE><PRE>apr_array_header_t *arr;arr = apr_array_make(mp, ARRAY_INIT_SZ, sizeof(const char*));</PRE></CODE></BLOCKQUOTE></P><P>The first argument is memory pool to use. Both array itself and elements are allocated in the memory pool. The second argument is an initial size of the array. The third argument is the size of element. The third argument is important for code readers, because it tells them what the container's type objects are. Unlike STL, apr_array_header_t is not type-safe. Therefore, it is important to declare the type clearly. In the sample above, we know that the array is an array of string pointers(const char*).</P><P><EM>REMARK</EM>: If you're familiar with the other libapr's APIs, you would feel weird because it doesn't use a result argument for a newly created object. Don't care.</P><P>We have to call apr_array_push() to append a new elememnt to the array.</P><P>/* excerpted from <A HREF="../sample/array-sample.c">array-sample.c</A> */<BLOCKQUOTE><CODE><PRE>*(const char**)apr_array_push(arr) = "123";</PRE></CODE></BLOCKQUOTE></P><P>Iteration is important interface for array. We can do as follows:</P><P>/* excerpted from <A HREF="../sample/array-sample.c">array-sample.c</A> */<BLOCKQUOTE><CODE><PRE>apr_array_header_t *arr;int i;for (i = 0; i &lt; arr->nelts; i++) {    const char *s = ((const char**)arr->elts)[i];    printf("%d: %s\n", i, s);}</PRE></CODE></BLOCKQUOTE></P><P>I have to say that the APIs above are not so intuitive. Fortunately they are very small interfaces, so you become familiar with them soon. Here, I show you generic form of array APIs. </P><P><BLOCKQUOTE><CODE><PRE>/* generic form of apr_array_header_t usages. 'T' is a type name. */apr_array_header_t *arr = apr_array_make(mp, ARRAY_INIT_SZ, sizeof(T));/* array of T objects *//* add an element */T elem;*(T*)apr_array_push(arr) = elem;/* iteration */for (i = 0; i &lt; arr->nelts; i++) {    T elem = ((T*)arr->elts)[i];}</PRE></CODE></BLOCKQUOTE></P><P>Let's take a look at <A HREF="../sample/array-sample.c">array-sample.c</A>. In <A HREF="../sample/array-sample.c">array-sample.c</A>, the array is an array of pointer. The array elements are allocated in the memory pool passed to apr_array_make(), but only pointer's memories are allocated. The objects that the pointers refer to are not allocated. So, the following sample code has a bug, because the strings in the array are not persistent.</P><P><BLOCKQUOTE><CODE><PRE>/* BUGGY sample of apr_array_header_t usage */void func(apr_array_header_t *str_arr){    char str[32];    strcpy(str, "foobar");    *(const char**)apr_array_push(str_arr) = str;/* BUG, if function caller uses this array */    return;}</PRE></CODE></BLOCKQUOTE></P><P>In most cases, the array itself and the elements are same lifetime, so that it might be better to allocate the objects in the same memory pool as follows:</P><P><BLOCKQUOTE><CODE><PRE>/* fix example of above bug */void func(apr_array_header_t *str_arr){    const char *str = apr_pstrdup(str_arr->pool, "foobar");    *(const char**)apr_array_push(str_arr) = str;}</PRE></CODE></BLOCKQUOTE></P><P>As you have seen, apr_array_header_t works with arbitrary type. Obviously, string array becomes more powerful than others. The reason is apr_array_pstrcat(). It works with string array, i.e. 'T is char*' or 'T is const char*' in the generic form above. apr_array_pstrcat() concatenates all the elements in the array and make a new string. The following example shows how to make one string from string chunks.</P><P><BLOCKQUOTE><CODE><PRE>/* pseudo code to make one string from string chunks */apr_array_header_t *html_arr = apr_array_make(mp, ARRAY_INIT_SZ, sizeof(const char*));*(const char**)apr_array_push(html_arr) = "&lt;html&gt;";*(const char**)apr_array_push(html_arr) = "&lt;head&gt;&lt;title&gt;foo&lt;/title&gt;&lt;/head&gt;";*(const char**)apr_array_push(html_arr) = "&lt;body&gt;&lt;ul&gt;";for (i = 0; i &lt; NUM_LIST; i++) {    *(const char**)apr_array_push(html_arr) = apr_psprintf(mp, "&lt;li&gt;%d&lt;/li&gt;", i);}*(const char**)apr_array_push(html_arr) = "&lt;/ul&gt;&lt;/body&gt;";*(const char**)apr_array_push(html_arr) = "&lt;/html&gt;";const char *html_str = apr_array_pstrcat(mp, html_arr, '\0');</PRE></CODE></BLOCKQUOTE></P><P>You might intend to sort the elements in the array. I mentioned that search on array isn't so effiecient above, but it can be efficient by bsearch(3) if it's sorted. I show you how to use qsort(3) with array.</P><P><BLOCKQUOTE><CODE><PRE>/* qsort(3) sample with array *//* we assume arr is an array of string */qsort(arr->elts, arr->nelts, arr->elt_size, cmp_string);/* compare function for qsort(3) */static int cmp_string(const void *v1, const void *v2){    const char *s1 = *(const char**)v1;    const char *s2 = *(const char**)v2;    return strcmp(s1, s2);}</PRE></CODE></BLOCKQUOTE></P><P>There is one more API that I have to point out. It is apr_array_pop(). By apr_array_pop(), we can use array as stack, which is known as first-in, last-out container. Please take a look at <A HREF="../sample/array-sample.c">array-sample.c</A> about the usage.</P><H2><A NAME="ss19.2">19.2</A> <A HREF="apr-tutorial.html#toc19.2">table</A></H2><P>Table is container of key-value pairs. It assumes key is character string. Value is allowed to be any type, but it is almost assumed to be character string. In the following description, we assume both key and value are character strings.</P><P>As container type, table has the following features.</P><P><BR><CENTER><TABLE BORDER><TR><TD>append</TD><TD>efficient(API support)</TD></TR><TR><TD>insert/prepend</TD><TD>inefficient(API support)</TD></TR><TR><TD>delete</TD><TD>inefficient(API support)</TD></TR><TR><TD>search(lookup)</TD><TD>almost efficient(API support)</TD></TR><TR><TD>iteration</TD><TD>efficient(API support)</TD></TR></TABLE><CAPTION>table</CAPTION></CENTER><BR></P><P>As you see, the features are almost same as array. This is natural, because table internally relies on array. Table has additional better features, though. First, table has better API supports. Second, table is more efficient for search(lookup). Table uses internal checksums for efficient search. Although it is not so efficient as hash table described later, it is better enough.</P><P>Table type is apr_table_t. We call apr_table_make() to create a new object as follows:</P><P>/* excerpted from <A HREF="../sample/table-sample.c">table-sample.c</A> */<BLOCKQUOTE><CODE><PRE>apr_table_t *tab;tab = apr_table_make(mp, TABLE_INIT_SZ);</PRE></CODE></BLOCKQUOTE></P><P>The first argument is memory pool to use. Like apr_array_header_t, both table itself and elements are allocated in the memory pool. The second argument is an initial size of table. As same as dynamic array, table size is dynamic. So, the initial size is just a hint.</P><P>There are four API to append elements to table as follows:</P><P>/* excerpted from apr_tables.h */<BLOCKQUOTE><CODE><PRE>APR_DECLARE(void) apr_table_set(apr_table_t *t, const char *key, const char *val);APR_DECLARE(void) apr_table_setn(apr_table_t *t, const char *key, const char *val);APR_DECLARE(void) apr_table_add(apr_table_t *t, const char *key, const char *val);APR_DECLARE(void) apr_table_addn(apr_table_t *t, const char *key, const char *val);</PRE></CODE></BLOCKQUOTE></P><P>The difference between 'set' and 'add' is that we allow multiple values for the same key or not. By 'set' calls, we don't allow multiple values for the same key. So, if another value already exists for the key, both apr_table_set() and apr_table_setn() over-write the old value. In contrast, both apr_table_add() and apr_table_addn() allow multiple values for the same key. Thus, the old values are still left.</P><P>The difference between 'with-n' and 'without-n' is that key and value's memories are duplicated or not. By 'without-n' calls, APIs duplicate both key and value string's memory. By 'with-n' calls, APIs don't duplicate them. 'With-n' APIs are more efficient, so when you know the string duplication is not necessary, you should use apr_table_setn() or apr_table_addn(). I show such examples:</P><P><BLOCKQUOTE><CODE><PRE>/* examples we can call apr_table_setn() or apr_table_addn() */apr_table_setn(tab, "foo", "bar"); /* string literals don't require duplication */apr_table_setn(tab, apr_pstrdup(mp, "foo"), apr_pstrdup(mp, "bar")); /* since the strings are already allocated in the same memory pool, we don't need double duplications */</PRE></CODE></BLOCKQUOTE></P><P>To search value by key, we call apr_table_get(). The prototype declaration is as follows:</P><P>/* excerpted from apr_tables.h */<BLOCKQUOTE><CODE><PRE>APR_DECLARE(const char *) apr_table_get(const apr_table_t *t, const char *key);</PRE></CODE></BLOCKQUOTE></P><P>If the key exists in the table, the associated value is returned. Otherwise, the return value is NULL. As mentioned earlier, 'add' APIs allow multiple values for a key. However, apr_table_get() can't return multiple values. It just returns the first value. Allowing multiple values does make sense when we iterate over the table, which I'll describe later.</P><P><EM>REMARK</EM>: Table key is case-insensitive. Historically, table was developed for HTTP headers.</P><P>apr_table_get()'s return value is just a pointer. I mean it doesn't duplicate the value string. So, the following sample code is buggy code.</P><P><BLOCKQUOTE><CODE><PRE>/* BUGGY sample */apr_table_setn(tab, "mykey", strdup("myval"));/* apr_table_setn() doesn't duplicate the strings */const char *v = apr_table_get(tab, "mykey");assert(strcmp(v, "myval") == 0);  /* v's value should be "myval" *//* v is a string allocated by strdup(3), so calling free(v) is a proper operation. * However, since the element still exists in the table, free(v) leaves an invalid memory in the table */free(v);</PRE></CODE></BLOCKQUOTE></P><P>On the other hand, when you remove an element from the table, you must free the string memory to get around memory leak.</P><P><BLOCKQUOTE><CODE><PRE>/* The similar situation above */const char *v = apr_table_get(tab, "mykey");apr_table_unset(tab, "mykey");free(v);/* XXX If we forget this, it causes memory leak... */</PRE></CODE></BLOCKQUOTE></P><P>A simple solution is to use memory pool. If all keys and values strings are allocated in the same memory pool of the table, all you have to do is to call apr_pool_destroy().</P><P>There are two ways to do iteration over table. One is to use callback function. The other is to use internal apr_array_header_t directly.</P><P>To use callback function for iteration, we call apr_table_do(). The prototype declaration and usage are as follows:</P><P>/* excerpted from apr_tables.h */<BLOCKQUOTE><CODE><PRE>APR_DECLARE_NONSTD(int) apr_table_do(apr_table_do_callback_fn_t *comp,                                     void *rec, const apr_table_t *t, ...);</PRE></CODE></BLOCKQUOTE></P><P>/* excerpted from <A HREF="../sample/table-sample.c">table-sample.c</A> */<BLOCKQUOTE><CODE><PRE>apr_table_do(tab_cb, "label1", tab, NULL);apr_table_do(tab_cb, "label2", tab, "key1", "key2", NULL);/* iteration with filters *//* table iteration callback */static int tab_cb(void *data, const char *key, const char *value){    const char *label = data;    printf("callback[%s]: %s %s\n", label, key, value);    return TRUE;/* TRUE:continue iteration. FALSE:stop iteration */}</PRE></CODE></BLOCKQUOTE></P><P>The first argument of apr_table_do() is callback function. The second argument is context object passed to callback function. The third argument is table object. The remained arguments work as filters for keys.</P>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -