第十四章 標准函數庫
第十五章 經典抽象數據類型(ADT)
int abs(int value); //求絕對值
long int labs(long int value); //對長整型求絕對值
div_t div(int denominator, int numerator);
第1個參數(分母)除以第2個參數(分子),產生商與余數。返回的結果是一個定義的結構體
typedef struct {
int quot/*商*/, rem/*余數*/;
} div_t;
quot = numerator/denominator
rem = numerator%denominator
ldiv_t ldiv(long int denominator, long int numerator);
typedef struct {
long quot, rem;
} ldiv_t;
與div功能一樣,適用於長整型。
這兩個除法函數中,當denominator為0或者當商或余數無法用返回類型表示時,這些函數的行為是未定義的(不同的編譯器不同,不一定導致定義域錯誤)
int rand(void);
返回一個范圍在0到RAND_MAX(至少為32768)之間的隨機數,為了得到一個更小范圍的隨機數,產先把這個函數的返回值根據所需范圍的大小進行取模,然後通過加上或減去一個偏移量對它進行調整。
void srand(unsigned int seed);
為了避免程序每次運行時獲得相同的隨機數,可以調用srand初始化隨機數生成器。種子一般使用時間:srand((unsigned int)tim(0))
如果種子相同,則每次(不是指同一運行過程中多次調用,而是指定程序運行結束後再次運行)程序運行產生的隨機數序列是一樣的。如果未使用srand就調用了rand,則默認會使用srand(1)先作初始化操作。
字符串轉換函數將字符串轉換成數值。這些函數的第一個參數如果包含了前導空白字符,它們都將被忽略,然後函數把合法字符轉換為指定類型的值。如果存在任何非法綴尾字符,它們也將被忽略。
int atoi(char const *string);
long int atol(char const *string);
上面兩個函數將以10為基數進行轉換,下面兩個可以指定基數。
long int strtol(char const *string, char **unused, int base);
unsigned long int strtoul(char const *string, char **unused, int base);
unused參數是一個二級指針,它指向了第一個未被轉換的字符指針,也以為NULL,如果為NULL則不會存儲未被成功轉換的綴尾非法串。
如果基數base為0,則數值的格式可以是十進制常量、八進制常量或十六進制常量,具體是什麼是根據它的格式推斷出來的。除基數可以為0外,還可以是2到36(包含兩者),字母a~z(A~Z)分別表示10~35的數字。一種特殊情況,如果基數為16,則數值可以以0x或0X開始(在符號位之後),這個前綴將被忽略。
如果這些函數的string參數並不是一個合法的數值串,函數就返回0。如果被轉換的值無法表示,則會設置errno為ERANGE值,並返回以下值:
strtol:如果值太大且為負數,返回LONG_MIN,如果太大且為正數,返回LONG_MAX
strtoul:如果值太大,返回ULONG_MAX
頭文件math.h包含了函數庫stdlib.h中剩余的數學函數的聲明,這些函數返回值以及絕大多數都是double類型。
如果一個函數的參數不在函數定義域之內,稱為定義域錯誤(domain error),例如:
sqrt(-5.0);
perror("sqrt");//sqrt: Domain error
當出現一個定義域錯誤時,函數返回一個由編譯器定義的錯誤值,並且在errno中存儲EDOM這個值。
如果一個函數的結果值過大或過小,無法用double類型表示,這稱為范圍錯誤(range error),例如:
printf("%f\n",exp(LONG_LONG_MAX));//1.#INF00
perror("exp");//exp: Result too large
由於結果值太大,函數返回 HUGE_VAL,它是一個在math.h中定義的double類型值。如果一個函數的結果值太小,無法用一個double表示,函數將返回0,這種情況也屬於范圍錯誤,但errno會不會設置為ERANGE取決於編譯器;
double exp(double x);
返回e值的x次冪,也就是ex
double log(double x);
返回x以e為底的對數,即自然對數。logex
double log10(double x);
返回x以10為底的對數。log10x
如果它們的參數為負,兩個對數函數都將出現定義域錯誤。
x以任意一個b為底的對數可以通過下面的公式進行計算:
logbx = logex/logeb
log39 = loge9/loge3 = log(9)/log(3) = 2
printf("%f\n", log(9)/log(3));//2.000000
double pow(double x, double y);
返回xy
double sqrt(double x);
double floor(double x);
返回不大於其參數的最大整數值,這個值以double的形式返回,是因為double能夠表示的范圍遠大於int。
double ceil(double x);
返回不小於其參數的最小整數值。
double fabs(double x);
返回絕對值
double fmod(double x, double y);
返回x除以y所產生的余數,由於操作符 % 只能用在兩個整數之間,所以提供了這個函數
這些函數和整數字符串轉換函數類似,只不守它們返回浮點值
double atof(char const *string);
double strtod(char const *string, char **unused);
clock_t clock(void);
返回從程序開始執行起處理器所消耗的時間,這個值可能是個近似值。如果需要更精確的值,你可以在main函數剛開始執行時調用clock,然後把以後調用clock時所返回的值減去前面的那個值。如果無法提供處理器時間,或太大無法用clock_t表示,則返回-1。
clock函數返回一個數字,它是由編譯器定義的,通常它是處理器時鐘滴答的次數,為了把這個值轉換為秒,你應該把它除以常量 CLOCKS_PER_SEC
typedef long clock_t;
#define CLOCKS_PER_SEC ((clock_t)1000)
time_t time(time_t *returned_vale);
如果參數是一個非NULL的指針,時間值也將通過這個指針進行存儲。如果機器無法提供當前日期與時間,或時間值太大,無法用time_t表示,則返回-1。正常一般返回的是從1970.1.1 00:00:00開始到今天的秒數(注,不像Java中返回的是毫秒數,如果time_t是一個long型,且結果是以秒為單位的,這個值會在2038年溢出)。注:標准並未規定函數的返回結果用秒來表示,不過從大多數的實現來看是秒。
typedef __time32_t time_t;
typedef __int32 __time32_t;
#define __int32 long
char *ctime(time_t const *time_value);
返回的格式固定為:Fri Apr 08 17:52:04 2011\n\0
用來存儲日期字符串的內存類型,許多編譯器使用一個靜態數組,因此,下一次調用ctime時,這個 字符串將被覆蓋,從下面的測試 s1 與 s2 就可以很明顯的看出來。
time_t *p;
time_t t = time(p);
printf("%d\n", *p);//1302256323
printf("%d\n", t);//1302256323
char *s1 = ctime(&t);
printf("%s", s1);//Fri Apr 08 17:34:01 2011
*p = *p + 1;//注,t的值並沒有被修改
printf("%d\n", *p);//1302256324
printf("%d\n", t);//1302256323
char *s2 = ctime(p);
printf("%s", s2);//Fri Apr 08 17:52:04 2011
//s1的值也被修改了,則說明s1與s2指向同一靜態存儲空間
printf("%s", s1);//Fri Apr 08 17:52:04 2011
ctime很可能是以以下這種方式實現的:
asctime(localtime(time_value));
double difftime(time_t time1, time_t time2);
計算 time1 – time2 的差,並把結果值轉換為秒,注意返回的是一個double類型。
下面兩個函數把一個time_t值轉換為一個tm結構,這樣我們就很方便地訪問日期和時間的各個部分了。gmtime將時間值轉換為UTC(格林尼漢標准時間),localtime函數把一個時間值轉換為當地時間。標准包含了這兩個函數,但它並沒有描述UTC和當地時間的實現之間的關系。
struct tm *gmtime(time_t const * time_value);
struct tm *localtime(time_t const *time_value);
struct tm {
int tm_sec; /* Seconds: 0-59 (K&R says 0-61?用於閏秒) */
int tm_min; /* Minutes: 0-59 */
int tm_hour; /* Hours since midnight: 0-23 */
int tm_mday; /* Day of the month: 1-31 */
int tm_mon; /* Months *since* january: 0-11 需加上1*/
int tm_year; /* Years since 1900 需要加上1900*/
int tm_wday; /* Days since Sunday (0-6) */
int tm_yday; /* Days since Jan. 1: 0-365 */
int tm_isdst; /* +1 Daylight Savings Time, 0 No DST,
* -1 don't know 夏令時標志*/
};
示例:當前當地日間為2011-13-12 10:29:52
time_t ti = time(NULL);
struct tm *t = gmtime(&ti);
printf("gmtime : %d-%d-%d %d:%d:%d\n", t->tm_year + 1900, t->tm_mday + 1,
t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
t = localtime(&ti);
printf("localtime : %d-%d-%d %d:%d:%d", t->tm_year + 1900, t->tm_mday + 1,
t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
輸出:
gmtime : 2011-13-12 2:29:52
localtime : 2011-13-12 10:29:52
你可以將上面的tm結構作為參數傳遞給下面的函數之一:
char *asctime(struct tm const *tm_ptr);
size_t strftime(char *string, size_t maxsize, char const *format,
struct tm const *tm_ptr);
asctime返回的格式與ctime是一樣的,ctime很可能是調用了asctime來實現自己的功能:
time_t *p;
time_t t = time(p);
//Tue Apr 12 17:47:48 2011
printf("%s", ctime(&t));
//Tue Apr 12 17:47:48 2011
printf("%s", asctime(localtime(&t)));
strftime函數把一個tm結構轉換為一個根據某個格式字符串而定的字符串。如果轉換結果字符串的長度小於maxsize參數,該字符串就被復制到第1個參數所指向的數組中,並返回字符串的長度,否則,函數返回-1且數組的內容是未定義的。
格式代碼
輸出
%%
%字符
%a
星期中的某天,星期幾的簡寫表示
%A
星期中的某天,星期幾的全寫表示
%b
月份,月份名的簡寫
%B
月份,月份名的全寫
%c
日期和時間,等效於“%x %X”
%x
日期,使用本地的日期格式
%X
時間,使用本地的時間格式
%d
一個月的第幾天(01-31)
%H
小時,以24小時格式(00-23)
%I
小時,以12小時格式(00-12)
%J
一年的第幾天(001-366)
%m
月數(01-12)
%M
分鐘(00-59)
%P
AM或PM
%S
秒(00-61)
%U
一年的第幾星期(00-53),以星期日為第1天
%W
一年的第幾星期(00-53),以星期一為第1天
%w
一星期的第幾天,星期日為第0天
%y
當前世紀的年份(00-99)
%Y
年份的全寫形式(例如,1984)
%Z
時區簡寫
time_t t = time(NULL);
struct tm *tm = localtime(&t);
char s[20];
//第二個參數只是指明數組的長度,防止數組越界
strftime(s, 20, "%Y-%m-%d %H:%M:%S", tm);
printf("%s\n", s);//2011-04-12 18:20:21
strftime(s, 20, "%c", tm);
printf("%s\n", s);//04/12/11 18:20:21
strftime(s, 20, "%x", tm);
printf("%s\n", s);//04/12/11
strftime(s, 20, "%X", tm);
printf("%s\n", s);//18:20:21
time_t mktime(struct tm *tm_ptr)
將tm結構轉換為一個time_t值。
setjmp和longjmp函數提供了一種類似goto的機制,它能從函數調用鏈的最底端直接跳轉到最上層函數(調用setjmp的函數),不必向調用鏈中的每個中間層函數返回一個錯誤標志。
int setjmp(jmp_buf state);
void longjmp(jump_buf state, int value);
setjmp函數的jmp_buf參數用來存儲初始化程序的狀態信息(堆棧指針的當前位置和程序的計數器),第一次調用setjmp函數時返回0,調用setjmp所處理的函數便成為了“頂層”函數。以後在頂層函數或其他任何它所調用的函數(不論是直接還是間接調用)內的任何調用longjmp函數,將導致這個被保存的狀態重新恢復(調用setjmp的語句會重新執行一次)。setjmp第一次調用時,即初始化時固定返回為0,第二次調用返回時,返回值為longjmp函數的第二個參數,它必須是一個非0。通過檢查setjmp函數的返回值,程序可以判斷是否調用了longjmp,還可以判斷是由哪個longjmp返回的(根據第二個參數值)。
下面程序用來檢測get_trans、process_trans或其他任何被這個函數直接調用的或間接調用的函數,如果調用了
longjmp(restart, 1)
類似的語句,程序則會直接從調用longjmp的函數直接返回到最上層函數(這裡是調用setjmp函數的main函數),而不會像Java裡的異常那樣一層層向上拋出異常,最後從第一次setjmp保存的程序狀態點開始重新執行(第一次調用setjmp函數的下一條語句)。
/*
** 事務結構的簡單聲明,以及處理事務的函數原型聲明
*/
typedef struct {
int a;
} Trans;
Trans *get_trans(void);
void process_trans(Trans *);
void write_data_to_file(void);
----trans.h
#include "trans.h"
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
/*
** 用於存儲setjmp的狀態信息變量.
*/
jmp_buf restart;
int main() {
int value;
Trans *transaction;
/*
** 創建一個在longjmp函數調用之後程序恢復執行的地點,
** 每次從這條語句開始恢復執行
*/
value = setjmp( restart );
/*
** 判斷是否是從longjmp返回的,如果是,還可以判斷是從
** 哪個longjmp返回的
*/
switch (value) {
default:
/*
** longjmp 被調用 -- 致命錯誤
*/
fputs("Fatal error.\n", stderr);
break;
case 1:
/*
** longjmp 被調用 -- 小錯誤
*/
fputs("Invalid transaction.\n", stderr);
/* 錯誤處理後繼續執行 */
case 0:
/*
** setjmp初始返回點: 執行正常處理,如果get_trans
** 、process_trans中調用了longjmp,則重新返回到
** 前面setjmp調用處並重新執行
*/
while ((transaction = get_trans()) != NULL)
process_trans(transaction);
}
/*
** Save data and exit the program
*/
write_data_to_file();
return value == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
信號表示一種事件,這種事件即可來自於程序外部,也可來自程序本身。
標准所定義的信號:
來源
信號
含義
程序內部(使用相同的數組每次運行必然發生)
SIGABRT
程序請求異常終止,由abort函數所引發的信號
SIGFPE
發生一個算術錯誤
SIGILL
檢測到非法指令
SIGSEGV
檢測到對內存的非法訪問
程序外部
SIGINT
收到一個交互性注意信號,用戶試圖中斷程序時發生,可以與程序交互
SIGERM
收到一個終止程序的請求,不配備信號處理函數
int raise(int sig);
顯示地引發一個信號,你可以調用這個函數對信號處理函數進行測試。
當一個信號發生時,程序可以使用三種方式對它作用反應。缺省反應是由編譯器定義的,通常是終止程;第二種是可以忽略信號;第三種程序可以設置一個信號處理函數,當信號發生時調用這個函數,signal函數就是用於指定程序希望采取的反應。
void (* signal(int sig, void(*handler)(int)))(int);
先看 signal(int sig, void(*handler)(int)):
第一個參數為上表中的信號之一,第二個參數信號處理函數,這個處理函數是一個函數指針,它所指向的函數接受一個整形參數且沒有返回值,當信號發生時,信號sig參數值會通過handler函數指針的參數傳遞給信號處理函數,這個處理函數則可以根據它的參數來處理不同的信號。
signal首先是一個函數(注意,不是函數指針,請參數函數指針章節),但它返回一個函數指針,這個返回的函數指針指向了一個沒有返回值但帶一個整型參數的函數,這個值就是在我們設置信號處理函數前原先所設置原信號處理函數,這樣我們就可以拿到原先的函數處理器,並可以恢復成原先以前的處理函數。如果這個函數調用失敗,則返回SIG_ERR值。
signal.h頭文件還定義了兩個宏,SIG_DFL和SIG_IGN,它們可以作為signal函數的第2個參數。SIG_DFL為編譯器對該信號的缺省反應,SIG_IGN表示信號將被忽略。
#define SIG_DFL ((__p_sig_fn_t) 0)
#define SIG_IGN ((__p_sig_fn_t) 1)
#define SIG_ERR ((__p_sig_fn_t) -1)
typedef void (*__p_sig_fn_t)(int);
int vprintf(char const * format, va_list arg);
int vfprintf(FILE *stream, char const * format, va_list arg);
int vsprintf(char *buffer, char const * format, va_list arg);
arg參數必須使用va_start進行初始化,這些函數都不需要調用va_end:
void func(int first, ...) {
va_list arg;
va_start(arg,first);
vprintf("%c %d\n", arg);//a 10
vfprintf(stdout, "%c %d\n", arg);//a 10
char buffer[10];
vsprintf(buffer, "%c %d\n", arg);
printf(buffer);//a 10
}
int main() {
func(0, 'a', 10);
}
void abort(void);
用於不正常終止一個正在運行的程序,它將引發SIGABRT信號
void atexit(void( func)(void));
可以把一些函數注冊為退出函數。當程序將要正常終止時(或者調用了exit,或者由於main函數返回),退出函數將被調用。
void exit(int status);
用於正常終止程序。如果程序以main函數返回一個值結束,那麼作用相當於這個值用作exit函數調用。
當exit函數被調用時,所有被atexit函數注冊為退出函數的函數將按照它們所注冊的順序被反序依次調用。然後,所有用於流的緩沖區被刷新,所有打開的文件被關閉,用tmpfile函數創建的文件被刪除,然後退出狀態返回給宿主環境,程序正常結束。
不要在atexit注冊的退出函數中再次調用exit,結果不可預料。
斷言就是聲明某種東西應該為真。
#define assert(e) ((e) ? (void)0 : _assert(#e, __FILE__, __LINE__))
void _assert (const char*, const char*, int);
如果這個宏的值為假(零),它就向標准錯誤打印一條診斷信息並終止程序,這條信息的格式是由編譯器定義的,但它將包含這個表達式和源文件的名字及斷言所在的行號;如果表達式為真(非零),它不打印任何東西,程序繼續執行。
例如,如果一個函數的某個參數不能為NULL:
void func(char * p) {
assert(p!=NULL);//Assertion failed: p!=NULL, file ..\src\setjmp.c, line 6
//指針不為空時才能繼續
//...
}
int main() {
func(NULL);
}
注意,一旦斷言失敗,程序就會終止。
當程序被完整地測試完成後,你可以消除所有的斷言,這樣提高效率。消除有兩種方式:使用 –DNDEBUG編譯器命令行選項或者在源文件中當頭文件assert.h被包含之前增加下面這個定義:
#define NDEBUG
這樣在編譯時會丟棄所有斷言,而又不需要將源文件中所有斷言實際刪除。
char * getenv(char const *name);
返回操作系統中定義的環境變量,如果沒有返回NULL。
//C:\Program Files\Java\jdk1.6.0_22
printf(getenv("java_home"));
注意標准並未定義一個對應的putenv函數,有些編譯器提供了這個函數,但為了可移植行最好不要使用。
int system(char const *command);
調用操作系統命令。system的行為以及返回值因編譯器而譯,但返回值通常是該命令的完成狀態。system可以用一個NULL參數,用於詢問命令處理器(或shell)是否實際存在,在這種情況,如果存在一個可用的命令處理器,system返回一個非零值,否則返回零。
printf("%d", system(NULL));//1
printf("%d", system("notepad"));//0
void qsort(void *base, size_t n_elements, size_t el_size, int(*compare)(
void const *, void const *));
第1個參數指向需要排序的數組,第2個參數指定數組中的元素個數,第3個參數指定每個元素的長度(以字節為單位),第4個參數是一個函數指針,相當於Java中的一個比較器。該函數可以對任意類型進行排序,但在比較器中需要強制轉換再比較。
void *bsearch(void const *key, void const *base, size_t n_elements,
size_t el_size, int(*compare)(void const *, void const *));
第1個參數指向需要查找的值,第2個參數指向需要排序的數組,第3個參數指定數組中的元素個數,第4個參數指定每個元素的長度(以字節為單位),第4個參數是一個函數指針,相當於Java中的一個比較器。返回一個指向查找到的數組元素指針,不存在時返回NULL。注意,查找前請一定要進行排序,否則結束不可預料。
#include <stdlib.h>
#include <string.h>
typedef struct {
char key[10]; //比較關鍵字
int other_data;
} Record;
//比較器
int r_compare(void const *a, void const *b) {
return strcmp(((Record *) a)->key, ((Record *) b)->key);
}
char * r(Record *, int);
void p(Record *, int);
int main() {
Record array[10] = { { { 0 } } };
Record key;
Record *ans;
char *s = r(array, 10);
p(array, 10);
strcpy(key.key, s);
qsort(array, 10, sizeof(Record), r_compare);
p(array, 10);
ans = bsearch(&key, array, 10, sizeof(Record), r_compare);
if (ans != NULL) {
printf("%s", (*ans).key);
} else {
printf("Not find");
}
return EXIT_SUCCESS;
}
void p(Record * re, int size) {
int i = 0;
for (; i < size; i++, re++) {
printf("%s-%d ", re->key, re->other_data);
}
printf("\n");
}
char * r(Record * re, int size) {
int i = 0;
srand(time(NULL));
int ri;
for (; ri = rand() % 26, i < size; i++, re++) {
*(re->key) = ri + 65;
re->other_data = ri;
}
return (--re)->key;
}
修改local可能影響庫函數的運行方式。
char * setlocal(int category, char const * locale);
用於修改整個或部分的local,category參數指定locale的哪個部分需要進行修改,它的值如下表:
值
修改
LC_ALL
整個locale
LC_COLLATE
對照序列,它將影響strcoll和strxfrm函數的行為
LC_CTYPE
定義於ctype.h中的字符處理函數所使用的字符類型
LC_MONETARY
在格式化貨幣值時使用的字符
LC_NUMERIC
在格式化非貨幣值時使用的字符。同時修改由格式化輸入/輸出函數和字符串轉換函數所使用的小數點符號
LC_TIME
strftime函數的行為
locale參數表示區域名稱,格式通常為:
language[_territory][.codeset]
language為ISO639中規定的語言代碼,territory為ISO3166中規定的國家/地區代碼,codeset為字符集名稱。
在Linux下,可以使用locale –a命令查看系統中所有已配置的locale。用不帶選項locale命令查看當前Shell中活動的locale。用locale –m命令查看locale系統支持的所有可用的字符集編碼。
Windows的locale不符合POSIX規范,比如采用GBK字符集的大陸中文,POSIX名字為 zh_CN.GBK,而在Windows終端中要使用 "Chinese_People's Republic of China.936"
當參數locale為NULL時,函數只取回當前正在使用的locale,返回這個當前locale,此時並不修改locale;當locale為 "" 時,根據環境的設置來設定locale,檢測順順序是:環境變量LC_ALL,每個單獨的locale分類LC_*,最後是LANG變量,修改後並反回修改後的locale。為了使用程序可以根據環境來靈活改變locale,一般都在程序的初始化階段加入下面代碼:
setlocale(LC_ALL,"")
當C語言程序初始化時(剛進入到main()時),locale初始化為默認的 "C" locale,其采用的字符編碼是所有本地ANSI字符集編碼的公共部分,即用來書寫C語言源程序的最小字符集(所以才取locale名叫:"C") 。當setlocale調用成功時,會返回當前正在使用locale的全名稱,如果失敗,會返回NULL。
在Liunx環境中(Windows上返回NULL):
char * loc = setlocale(LC_ALL, "zh_CN.UTF-8");
printf("%s\n", loc);//zh_CN.UTF-8
localeconv函數用於獲得根據當前的locale對非貨值和貨幣值進行合適的格式化所需要的信息。
struct lconv * localeconv(void);
lconv結構包含兩種類型的成員:字符和字符指針。如果一個字符成員為 CHAR_MAX,那這個值就在當前的locale中不可用(或不使用),對於字符指針成員指向一個空字符串(不是NULL),意義與前面相同。
struct lconv
{
char* decimal_point;//用作小數點的字符,該值一定不能是空字符串
char* thousands_sep;//用作分隔小數點左邊各組數的符號
char* grouping;//指定小數點左邊多少個數字組成一組,如3位一組,則grouping=”\3”,使用的是八進制表示,如果是4位一組grouping=”\4”,如果僅挨著小數點的3位一組,次4位一組,則grouping=”\3\4”
char* int_curr_symbol;
char* currency_symbol;//本地貨幣符號
char* mon_decimal_point;//貨幣小數點
char* mon_thousands_sep;//用於分隔貨幣小數點左邊各組數字的字符
char* mon_grouping; //指定出現在貨幣小數點左邊每組數字的數字個數,與grouping類似
char* positive_sign;//用於提示非負貨幣值的字符串
char* negative_sign;//用於提示負貨幣值的字符串
char int_frac_digits;
char frac_digits;//出現在小數點右邊的數字個數
char p_cs_precedes;//如果currency_symbol出現在一個非負值之前,其值為1(使用 '\1' 賦值方式);如果出現在後面,其值為0(使用 '\0' 賦值方式)
char p_sep_by_space; //如果currency_symbol和非負值之間用一個空格分隔,其值為1(使用 '\1' 賦值方式),否則為0(使用 '\0' 賦值方式)
char n_cs_precedes; //如果currency_symbol出現在一個負值之前,其值為1(使用 '\1' 賦值方式);如果出現在後面,其值為0(使用 '\0' 賦值方式)
char n_sep_by_space; //如果currency_symbol和負值之間用一個空格分隔,其值為1(使用 '\1' 賦值方式),否則為0(使用 '\0' 賦值方式)
char p_sign_posn;//提示positive_sign出現在一個非負值的位置。允許下列值:
0 貨幣符號和值兩邊的括號 (使用 '\0' 賦值方式)
1 正號出現在貨幣符號和值之前(使用 '\1' 賦值方式)
2 正號出現在貨幣符號和值之後(使用 '\2' 賦值方式)
3 正號緊鄰貨幣符號之前(使用 '\3' 賦值方式)
4 正號緊鄰貨幣符號之後(使用 '\4' 賦值方式)
char n_sign_posn;//提示negative_sign出現在一個負值中的位置,用於p_sign_posn的值也可用於此處
};
注,該函數只是用於獲取當前區域中格式化數字及貨幣格式化的有關信息,這就允許程序員根據當前的lconv中的信息實現一些應用程序特定的轉換和格式化函數,並且它們在不同的區域間具有移植性,從而避免了向標准C中添加區域特定的轉換工具函數的必要性。
int strcoll(const char *s1, const char *s2);
size_t strxfrm(char *dest, const char *src, size_t len);
strcoll函數根據setlocale所設置的LC_COLLATE的區域比較約定進行字符比較,返回的結果與strcmp一樣。
strxfrm函數把字符串src轉換為另一個字符串,存儲在字符數組dest中,經過轉換後的字符可以直接使用strcmp來比較。該函數返回存儲這轉換後的字符串所需要的字符個數(不包括結尾的null字符)。
抽象數據類型就是指數據結構,因為數據結構是與語言無關的,所以叫抽象的。
前面章節以討論過鏈表結構,現討論堆棧、隊列、樹等。
實現這些抽象數據類型時,需要分配內存。有三種分配方式:靜態數組、動態分配的數組、動態分配的鏈式結構。
動態分配數組可以使用malloc函數:
char * s = malloc(10);
s[0]='a';
s[1]='b';
s[2]='\0';
printf(s);//ab
鏈式的動態分配可以在每個元素需要時才單獨進行分配,分配最靈活,但在鏈式結構中訪問一個特定元素的效率不如數組。
/*
** 堆棧接口
*/
#define STACK_TYPE int /* 棧中的元素類型,定義成宏便於類型方便的更換 */
/*
** push:入棧
*/
void push(STACK_TYPE value);
/*
** pop:出棧,並移除
*/
void pop(void);
/*
** top:返回棧頂元素,但不移除
*/
STACK_TYPE top(void);
/*
** 棧是否為空
*/
int is_empty(void);
/*
** 棧是否已滿
*/
int is_full(void);
— — stack.h
所有不屬於外部接口的內容都被聲明為static,這可以防止用戶使用預定義接口之外的任何方式訪問堆棧中的值。