C語(yǔ)言標(biāo)準(zhǔn)庫(kù)(1)

姓名:呂彬 學(xué)號(hào):1613014035

【嵌牛導(dǎo)讀】標(biāo)準(zhǔn)庫(kù)(Standard Library)是C語(yǔ)言重要的一部分,不過(guò)學(xué)習(xí)C語(yǔ)言這么長(zhǎng)時(shí)間,都沒(méi)有細(xì)致的了解過(guò)標(biāo)準(zhǔn)庫(kù)到底中包含哪些內(nèi)容,這幾天打算來(lái)仔細(xì)看看這部分內(nèi)容。

【嵌牛鼻子】C語(yǔ)言標(biāo)準(zhǔn)庫(kù)有各種不同的實(shí)現(xiàn),比如最著名的glibc, 用于嵌入式Linux的uClibc,還有ARM公司的自己的C語(yǔ)言標(biāo)準(zhǔn)庫(kù)及精簡(jiǎn)版的MicroLib等。不同標(biāo)準(zhǔn)庫(kù)的實(shí)現(xiàn)并不相同,而且提供的函數(shù)也不完全相同,不過(guò)有一個(gè)它們都支持的最小子集,這也就是最典型的C語(yǔ)言標(biāo)準(zhǔn)庫(kù)。

【嵌牛提問(wèn)】C語(yǔ)言標(biāo)準(zhǔn)庫(kù)包含哪些內(nèi)容呢?

【嵌牛正文】這個(gè)C語(yǔ)言標(biāo)準(zhǔn)庫(kù)中一共包含15個(gè)頭文件,粗略的按常用程度排序列舉如下:

圖片發(fā)自簡(jiǎn)書(shū)App


本文總結(jié)的是不完整的C標(biāo)準(zhǔn)庫(kù),僅列舉一些常用且最重要的部分。ime.h日期和時(shí)間操作。需要特別注意的是,書(shū)中使用的time_t時(shí)間戳標(biāo)準(zhǔn)是從1900年1月1日午夜開(kāi)始的,這與目前廣泛使用的UNIX時(shí)間戳不一樣,也和Glibc的實(shí)現(xiàn)不一樣,書(shū)中是通過(guò)_TBIAS這個(gè)宏定義偏置量來(lái)解決這個(gè)問(wèn)題的,為了簡(jiǎn)單起見(jiàn),此處對(duì)此進(jìn)行了改寫(xiě),忽略了偏置問(wèn)題,直接將其修改為與UNIX時(shí)間戳一樣。使用方法通常使用time(NULL)獲取一個(gè)time_t類(lèi)型的UNIX時(shí)間戳,這一般是一個(gè)32位整數(shù)(signed int),指的是從1970年1月1日午夜至今的秒數(shù),大約可以表示到2038年。如果要獲取更精確的時(shí)間,可使用clock()函數(shù)。其余函數(shù)用于在幾種不同數(shù)據(jù)結(jié)構(gòu)間進(jìn)行轉(zhuǎn)換,根據(jù)需要選取即可,其中tm類(lèi)型的定義一般是這樣的:1234567891011 struct tm {? ? int tm_sec;? ? /* [0, 60], 1 leap second */? ? int tm_min;? ? /* [0, 59] */? ? int tm_hour;? ? /* [0, 23] */? ? int tm_mday;? ? /* [1, 31] */? ? int tm_mon;? ? /* [0, 11] */? ? int tm_year;? ? /* Years since 1900 */? ? int tm_wday;? ? /* [0, 6], Sunday, Monday... */? ? int tm_yday;? ? /* [0, 365], days since January 1th */? ? int tm_isdst;? /* 夏令時(shí)標(biāo)志,無(wú)效則為0 */}需要注意的是,以上只是time_t的最小實(shí)現(xiàn),實(shí)際Glibc 2.23版本的代碼中除了上述成員外還添加了其它字段。tm_year是從1900年開(kāi)始的,并不是和UNIX時(shí)間戳相同的1970年。實(shí)現(xiàn)方法time()和clock()函數(shù)是依賴(lài)于具體實(shí)現(xiàn)的,此處不作分析。difftime()函數(shù)返回兩個(gè)時(shí)間戳之間的差值,考慮到time_t可能會(huì)被定義為無(wú)符號(hào)整數(shù),故需要先比較二者的大?。?23 double difftime(time_t t1, time_t t0) {? ? return (t0 <= t1 ? (double)(t1 - t0) : -(double)(t0 - t1));}tm與time_t間的轉(zhuǎn)換函數(shù)是<time.h>中的重點(diǎn),這里主要來(lái)看一下gmtime()和mktime()的實(shí)現(xiàn)方法。下列代碼在書(shū)中給出的代碼基礎(chǔ)上進(jìn)行了些改寫(xiě),主要是做了些精簡(jiǎn),沒(méi)有考慮夏令時(shí)等問(wèn)題。雖然以下兩段代碼比Glibc中的實(shí)現(xiàn)要簡(jiǎn)單得多,不過(guò)經(jīng)測(cè)試完全可以正常使用。1234567891011121314151617181920212223242526272829303132333435363738394041 static const short lmos[] = { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 };static const short mos[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };#define MONTAB(year) ((year & 0x03) == 2 ? lmos : mos)struct tm * gmtime(time_t * timer) {? static struct tm ts;? int year;? int days;? int secs;? secs = *timer;? days = secs / 86400;? ? ? ? ? // 獲取天數(shù)? ts.tm_wday = (days + 4) % 7;? // 1970年1月1日是星期四? int dDay;? /* days / 365 先求出year的初步估計(jì),因?yàn)殚c年的存在不一定準(zhǔn)確(可能會(huì)多1年) */? /* (year + 1) / 4 求出因閏年多出來(lái)的天數(shù) */? /* days與year年初的天數(shù)比較,若days小于它,說(shuō)明year估計(jì)有誤,需要減去1年 */? for (year = days / 365; days < (dDay = (year + 1) / 4 + 365 * year);)? ? year--;? days -= dDay;? ? ? ? ? ? // 將days變成1年中的天數(shù)? ts.tm_year = year + 70;? // tm_year是從1900年開(kāi)始的? ts.tm_yday = days;? ? ? ? // 總天數(shù)減去年初的天數(shù)? /* 從最后一個(gè)月開(kāi)始,逐步向前尋找正確的月份,pm[mon]得到月初的天數(shù) */? int mon;? const short * pm = MONTAB(year);? int tmp = (year & 0x03) == 2;? for (mon = 11; days < pm[mon]; mon--);? ts.tm_mon = mon;? ts.tm_mday = days - pm[mon] + 1;? /* 根據(jù)secs依次求出小時(shí)、分鐘和秒 */? secs %= 86400;? ts.tm_hour = secs / 3600;? secs %= 3600;? ts.tm_min = secs / 60;? ts.tm_sec = secs % 60;? ? return &ts;}123456789101112131415161718192021222324252627282930 static const short lmos[] = { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 };static const short mos[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };#define MONTAB(year) ((year & 0x03) == 2 ? lmos : mos)time_t mktime(struct tm * timeptr) {? int year, days, secs;? // 檢查參數(shù)有效性,不使用tm_yday & tm_wday#ifndef NDEBUG? if (timeptr->tm_hour < 0 || timeptr->tm_hour > 23)? ? return -1;? if (timeptr->tm_mday < 1 || timeptr->tm_mday > 31)? ? return -1;? if (timeptr->tm_min < 0 || timeptr->tm_min > 59)? ? return -1;? if (timeptr->tm_mon < 0 || timeptr->tm_mon > 11)? ? return -1;? if (timeptr->tm_sec < 0 || timeptr->tm_sec > 60)? ? return -1;? if (timeptr->tm_year < 70 || timeptr->tm_year > 138)? ? return -1;#endif? year = timeptr->tm_year - 70;? days = (year + 1) / 4 + 365 * year;? days += MONTAB(year)[timeptr->tm_mon] + timeptr->tm_mday - 1;? secs = 3600 * timeptr->tm_hour+ 60 * timeptr->tm_min + timeptr->tm_sec;? return (86400 * days + secs);}需要指出的是,上面這個(gè)mktime()函數(shù)沒(méi)有考慮時(shí)區(qū)的問(wèn)題,而標(biāo)準(zhǔn)的mktime()函數(shù)實(shí)現(xiàn)的是將由tm表示的地方時(shí)轉(zhuǎn)換為time_t表示的GMT時(shí)間,所以二者并不等價(jià)。在1970~2038年這個(gè)范圍內(nèi),閏年規(guī)律符合簡(jiǎn)單的4年一閏,所以可以用(year & 0x03) == 2來(lái)進(jìn)行閏年判斷。其余asctime()、ctime()等函數(shù)用于返回格式化的時(shí)間字符串,其原理和sprintf()等函數(shù)大同小異,在此不作分析。ctype.h包含字符測(cè)試及大小寫(xiě)轉(zhuǎn)換函數(shù)。使用方法提供了若干isxxxx()函數(shù)用于判斷字符類(lèi)型,并提供了大小寫(xiě)轉(zhuǎn)換函數(shù),具體函數(shù)列表見(jiàn):C語(yǔ)言標(biāo)準(zhǔn)庫(kù)總結(jié)需要說(shuō)明的是,字符集的具體定義和區(qū)域設(shè)置有關(guān),不過(guò)常用的就是英文的情況,因?yàn)檫@些函數(shù)也無(wú)法處理中文編碼。另外,函數(shù)接受的參數(shù)是一個(gè)int類(lèi)型的整數(shù),不過(guò)只有unsigned char類(lèi)型所能表示的值加上EOF宏定義的值(一般為-1)是有效的,傳入其它值的行為是未定義的。實(shí)現(xiàn)方法出于效率考慮,標(biāo)準(zhǔn)庫(kù)中的實(shí)現(xiàn)方法是基于轉(zhuǎn)換表的,這里不列舉具體使用的轉(zhuǎn)換表了,僅描述一下設(shè)計(jì)思路。首先將整個(gè)字符集合劃分為若干個(gè)合理設(shè)計(jì)的子集,如數(shù)字(0~9)、小寫(xiě)字母(a~z)、大寫(xiě)字母(A~Z)等,每一類(lèi)用一個(gè)比特位來(lái)表示,這樣就可以得到如下宏定義:1234 #define _XD? 0x01? /* '0'-'9', 'A'-'F', 'a'-'f' */#define _UP? 0x02? /* 'A'-'Z' */#define _SP? 0x04? /* space */// ......任何一個(gè)字符都屬于某一子集(或某幾個(gè)子集)中,這樣就可以根據(jù)以上宏定義得到這個(gè)字符的編碼了,將全體字符編碼構(gòu)成一個(gè)數(shù)組,這就是所謂的轉(zhuǎn)換表,書(shū)中這個(gè)數(shù)組的名字叫做_Ctype。這樣一來(lái),要判斷某個(gè)字符是否屬于某個(gè)子集就很簡(jiǎn)單了,只要檢查這個(gè)字符在轉(zhuǎn)換表中對(duì)應(yīng)值的特定位是否被置位了就可以了,比如檢查一個(gè)字符是否是大寫(xiě)字母:123 int isupper(int c) {? ? return (_Ctype[c] & _UP);}大小寫(xiě)間的轉(zhuǎn)換也是基于轉(zhuǎn)換表的,這個(gè)轉(zhuǎn)換表相當(dāng)于在原始ACSII表的基礎(chǔ)上將大寫(xiě)字母替換為小寫(xiě)字母(或相反)得到的。關(guān)于區(qū)域編碼的問(wèn)題此處從略。stdarg.h用于處理可變參數(shù)。使用方法可變參數(shù)函數(shù)的定義類(lèi)似這樣:1234567891011 #include <stdarg.h>void fun(int parmN,...) {? ? va_list ap;? ? va_start(ap, parmN);? ? //......? ? int a = va_arg(ap, int);? ? double b = va_arg(ap, double);? ? //......? ? va_end(ap);}必須要有至少一個(gè)固定參數(shù),習(xí)慣上把最后一個(gè)固定參數(shù)叫做parmN。在函數(shù)中先調(diào)用va_start()初始化va_list,之后就可以通過(guò)va_arg()依次獲取各參數(shù),最后調(diào)用va_end()即可。需要注意的是,在可變參數(shù)中,應(yīng)用的是“加寬”原則,也就是float會(huì)被擴(kuò)展成double,char、short等會(huì)被擴(kuò)展成int,也就是說(shuō),函數(shù)中只該使用以下這些表達(dá)式:123 va_arg(ap, double);va_arg(ap, int);va_arg(ap, unsigned int);實(shí)現(xiàn)方法12345 typedef char * va_list;#define va_start(ap, A)? (void)((ap) = (char *)&A)#define va_end(ap)? ? ? (void)(0)#define va_arg(ap, T)? ? (*(T *))((ap += sizeof(T)) - sizeof(T))這里給出的代碼是簡(jiǎn)化版代碼,沒(méi)有考慮存儲(chǔ)空隙及對(duì)齊問(wèn)題,僅用來(lái)說(shuō)明基本原理。assert.h提供斷言。使用方法在需要使用斷言的地方加入assert(x)即可,x是一個(gè)int,若x為零斷言成立,此時(shí)程序會(huì)向標(biāo)準(zhǔn)錯(cuò)誤流輸出一條包含出錯(cuò)行號(hào)等的錯(cuò)誤信息并調(diào)用abort()函數(shù)終止程序的運(yùn)行。assert(x)返回void。一般只有在程序調(diào)試時(shí)才需要終止程序運(yùn)行,發(fā)布時(shí)應(yīng)該去掉這個(gè)功能,為實(shí)現(xiàn)這一目的,可通過(guò)定義NDEBUG這個(gè)宏來(lái)實(shí)現(xiàn),一般使用編譯器預(yù)定義。實(shí)現(xiàn)方法為了對(duì)NDEBUG作出正確回應(yīng),頭文件的基本結(jié)構(gòu)如下:123456 #undef assert? ? /* remove existing definition */#ifdef NDEBUG#define assert (test) ((void) 0)? /* passive form */#else#define assert (test) ...? ? ? ? /* active form */#endif其中active form的定義如下:1234 void _Assert(char *);#define _STR(x) _VAL(x)#define _VAL(x) #x#define assert(test)? ((test) ? (void) 0 : _Assert(__FILE__":"_STR(__LINE__)" "#test))_Assert()是一個(gè)隱藏庫(kù)函數(shù),用于調(diào)用<stdio.h>中的其它庫(kù)函數(shù)輸出錯(cuò)誤信息并調(diào)用abort()函數(shù),這個(gè)很簡(jiǎn)單,沒(méi)有什么問(wèn)題,上述代碼的關(guān)鍵在于后面幾行宏定義上。__FILE__及__LINE__這兩個(gè)宏是由編譯器定義的,代表當(dāng)前文件名及當(dāng)前代碼行號(hào),__FILE__是一個(gè)字符串,而__LINE__是一個(gè)十進(jìn)制整數(shù)。_STR()及_VAL()這兩個(gè)宏神奇的實(shí)現(xiàn)了將一個(gè)整數(shù)常量轉(zhuǎn)換為字符串字面量的功能,二者缺一不可,也就是說(shuō),下面這個(gè)寫(xiě)法是錯(cuò)誤的:1 #define _STR(x) #x使用這個(gè)寫(xiě)法的話,_STR(__LINE__)得到的是"__LINE__"#就是把宏參數(shù)進(jìn)行字符串處理。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容