C標準函數(shù)庫
C標準函數(shù)庫是所有符合標準的頭文件的集合,以及常用的函數(shù)庫實現(xiàn)程序,例如I/O 輸入輸出和字符串控制。不像 COBOL、Fortran 和 PL/I等編程語言,在 C 語言的工作任務(wù)里不會包含嵌入的關(guān)鍵字,所以幾乎所有的 C 語言程序都是由標準函數(shù)庫的函數(shù)來創(chuàng)建的。
每一個函數(shù)的名稱與特性會被寫成一個電腦文件,這個文件就稱為頭文件,但是實際的函數(shù)實現(xiàn)是被分存到函數(shù)庫文件里。頭文件的命名和領(lǐng)域是很常見的,但是函數(shù)庫的組織架構(gòu)也會因為不同的編譯器而有所不同。標準函數(shù)庫通常會隨附在編譯器上。因為 C 編譯器常會提供一些額外的非 ANSI C 函數(shù)功能,所以某個隨附在特定編譯器上的標準函數(shù)庫,對其他不同的編譯器來說,是不兼容的。
本篇介紹若干常用的標準C函數(shù)的用法,主要介紹stdio(標準輸入輸出)、math(數(shù)字函數(shù)庫)、time(時間函數(shù)庫)、stdlib(標準函數(shù)庫)string(標準字符串函數(shù))等。
1.1 stdio.h
標準輸入/輸出函數(shù),stdio即standard input/output。其中文件操作相關(guān)API在單獨一章中介紹。
#include 《stdio.h》
char getchar(void); // 控制臺輸入一個字符
int putchar(int c); // 控制臺輸出一個字符
char *gets(char *s); // 控制臺輸入一個字符串
int puts(const char *s); // 控制臺輸出一個字符串
int printf(const char *format, 。。。); // 控制臺格式化輸出
int scanf(const char *format, 。。。); // 控制臺格式化輸入
int sprintf(char *s, const char *format, 。。。); // 字符串格式化輸出
int sscanf(const char *s, const char *format, 。。。); // 字符串格式化輸入
1.1.4 sprintf與sscanf
適用于string版本的格式化輸入/輸出,其目標不是控制臺,而是一個字符串。這兩個函數(shù)非常有用。
用sprintf格式化一個字符串,例如,
[cpp] view plain copychar buf [256];
sprintf (buf, “Name: %s, Age: %d, Height: %.2f \n”, “LiMing“, 30, 1.68 );
此代碼將目標buf格式化為: Name: LiMing, Age 30, Height: 1.68
用sscanf從一個具有格式的字符串中提取固定字段,和scanf的用法類似,要求在格式上要嚴格匹配。以下代碼從字符中提供取年月日的值。
[cpp] view plain copychar* src = ”2014-12-11“;
int year, month, day;
int n = sscanf ( src,
”%d-%d-%d“,
&year, &month, &day);
if( n == 3)
{
// 成功提取了所有字段
}
1.2 math.h
提供了一系列數(shù)學(xué)相關(guān)的函數(shù),如三角函數(shù)、指數(shù)/對數(shù)、冪/根號等。
#include 《math.h》
double abs(double x); // 取絕對值
double cos(double x); // 余弦cos, 參數(shù)是弧度值
double sin(double x); // 正弦sin, 參數(shù)是弧度值
double tan(double x); // 正切 tan, 參數(shù)是弧度值
double ceil(double x); // 向上取整, 即不小于x的最小整數(shù)
double floor(double x); // 向下取整, 即不大于x的最大整數(shù)
double exp(double x); // 求ex
double log(double x); // ln(x), 以e為底的對數(shù)
double log10(double x); // lg(x), 以10為底的對數(shù)
double pow(double x, double y); // 求冪xy
double sqrt(double x); // 求平方根 x1/2
需要補充說明的是,這里所列的幾乎所有函數(shù)都至少有2個版本,分別是double型和float型參數(shù)。例如,
[cpp] view plain copydouble sqrt(double x);
float sqrt(float x);
以下代碼編譯錯誤,
[cpp] view plain copydouble result = sqrt (16); // 編譯錯誤
為什么會有編譯錯誤呢?因為字面常量16是int型,在匹配函數(shù)時,sqrt(float)與sqrt(double)均被匹配,int可以隱式轉(zhuǎn)換成float和double。必須要顯示把參數(shù)強轉(zhuǎn)為double或float。
如果要對一個整數(shù)調(diào)用sqrt函數(shù),那么就必須顯式的強轉(zhuǎn)成double或float,例如,
[cpp] view plain copydouble result = sqrt ((double)16); // OK
或
float result = sqrt ((float)16); // OK
1.2.1 abs求絕對值
例如 ,
[cpp] view plain copydouble a = abs ( -12.34);
1.2.2 cos/sin/tan三角函數(shù)
其單位都是弧度值。例如,
[cpp] view plain copy#define PI 3.1415926535898
double ret = sin ( PI / 2); // sin (pi/2)結(jié)果應(yīng)為1
1.2.3 ceil /floor取整
用于向上/向下取整。例如,
[cpp] view plain copydouble ci = ceil ( 12.87 ); // +13
double fi = floor(12.87); // +12
ci = ceil ( -12.87 ); // -12
fi = floor( -12.87); // -13
1.2.4 exp / log / log10 / pow / sqrt指數(shù)/對數(shù)/冪/平方根
點擊查看此節(jié)內(nèi)容,第16章
1.3 time.h
time.h中提供了時間/日期相關(guān)的函數(shù)。這里僅列出常用的幾個函數(shù)。
#include 《time.h》
struct tm *localtime(const time_t *tod); // 當“秒值”轉(zhuǎn)成“年月日時分秒”
time_t mktime(struct tm *tptr); // 將“年月日時分秒”轉(zhuǎn)成“秒值”
time_t time(time_t *tod); // 取得當前時間
其中,需要介紹time_t和struct tm這兩個類型。
1.3.1 time_t
time_t是一個typedef的類型,目前在各種操作系統(tǒng)上time_t類型都是一個整數(shù)類型,差不多就是
typedef long time_t;
這種類似的定義,在各種場合都可以認為它是int型。當然,保險起見也可以顯式的強轉(zhuǎn)一下。它的單位是秒。
[cpp] view plain copytime_t start = 1000;
time_t end = 1020;
printf(”time eclipse: %d seconds !\n“, (int)(end - start));
1.3.2 struct tm
tm是一個結(jié)構(gòu),它的定義是,
struct tm
{
int tm_sec; /* seconds after the minute - [0,59] */
int tm_min; /* minutes after the hour - [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] */
int tm_year; /* years since 1900 */
int tm_wday; /* days since Sunday - [0,6] */
int tm_yday; /* days since January 1 - [0,365] */
};
它用于表示日期/時間,有幾個字段組成:年,月,日,時,分,秒,weekday和yearday。其中,
年:從1900開始算,tm_year = 114表示年份 1900 + 114 = 2014
月:范圍是[0,11], tm_mon = 11表示月份 12
日:范圍是[1,31], tm_mday = 24表示該月的第24天
時: 范圍是[0,23],tm_hour = 13表示下午13時
分: 范圍是[0,59], tm_min = 40表示第40分鐘
秒:范圍是[0,59], tm_sec = 40表示第40秒鐘
tm_wday: 范圍是[0,6],星期日是0,星期一是1,。。。 ,星期六是6
tm_yday: 范圍是[0,365],tm_yday=299,表示當年的第300天
當tm_min和tm_sec都為0時,表示整點時間。如12:00:00。
例如,2014-12-11 11:47:12這個時間可以用下面的代碼賦值:
[cpp] view plain copytm info;
info.tm_year = 2014 - 1900; // 2014年
info.tm_mon = 12 - 1; // 12月
info.tm_mday = 11; // 11日
info.tm_hour = 11; // 11時
info.tm_min = 47; // 47分
info.tm_sec = 12; // 12分
1.3.3 time取得系統(tǒng)當前時間
time函數(shù)可以取得系統(tǒng)當前時間,返回值是一個秒值。例如,
[cpp] view plain copytime_t now = time (NULL);
為什么一個整數(shù)秒值可以表示當前的時間呢?是這么規(guī)定的,time函數(shù)返回的是自1970-1-1 00:00:00這個時間點開始至當前時刻的時間差。它是一個比較大的整數(shù),例如1418270153表示的是2014-12-11 11:55:53這個時刻。
利用time函數(shù)時可以計算程序運行了多少時間,如下面的代碼:
[cpp] view plain copytime_t start = time (NULL);
。。。 DoSomething 。。。
time_t end = time (NULL);
printf(”Time cost : %d seconds“, end - start );
當然這個DoSomething要運行相當時間才行,因為time的粒度較大,返回是秒值。如果你的DoSomething只耗費了幾毫秒的話,那么用time根本無法衡量。不過,可以成倍的量化一下,比較,將DoSomething連續(xù)運動10000次,看需要的總時間,然后再平均一下得出單次需要的時間。
[cpp] view plain copytime_t start = time (NULL);
for( int i=0; i《 10000; i++)
{
。。。 DoSomething 。。。
}
time_t end = time (NULL);
int avg = (end - start ) / 10000;
1.3.4 localtime()得到年月日時分秒
雖然用一個time_t整數(shù)來表示當前系統(tǒng)時間是比較方便的,但有時候還是希望能轉(zhuǎn)化成年月日時分秒的形式來顯示,畢竟肉眼無法直接看一個time_t值到底是哪一個日期。
localtime函數(shù)可以將time_t所表示的時間轉(zhuǎn)化成年月日時分秒,例如,
[cpp] view plain copytime_t t = time(NULL);
tm info = *localtime(&t);
printf(”%04d-%2d-%02d %02d:%02d:%02d \n“,
info.tm_year + 1900,
info.tm_mon + 1,
info.tm_mday,
info.tm_hour,
info.tm_min,
info.tm_sec);
localtime的返回值是tm*類型,應(yīng)該用一個tm變量將內(nèi)容保存起來。
事實上,用time_t來記錄時間更方便,只用一個整數(shù)(占4個字節(jié))就表達了日期和時間信息。在保存和傳輸?shù)臅r候,應(yīng)該盡量用time_t類型。只是在最終顯示的時候,用localtime轉(zhuǎn)成人類易讀的yyyy-mm-dd HH:MM:SS格式。
1.3.5 mktime構(gòu)造時間
當已知了年月日時分秒信息,可以用mktime換算成time_t值。例如,把2014-12-11 11:47:12轉(zhuǎn)成time_t值,
點擊查看此節(jié)內(nèi)容,第16章
1.4 stdlib.h
這一節(jié)將介紹stdlib.h里提供的API,下面列表其中主要的幾個函數(shù):
#include 《stdlib.h》
double atof(const char *s);
int atoi(const char *s);
int rand(void);
void srand(unsigned int seed);
int system(const char *s);
1.4.1 atoi / atof 字符串轉(zhuǎn)成數(shù)字
例如,
[cpp] view plain copyint n = atoi(”1280“);
double f = atof(”12.80“);
其實完全可以用sscanf來完成相同的事情,例如,
[cpp] view plain copyint n;
double f;
sscanf(”1280“, ”%d“, &n);
sscanf(”12.80“, ”%f“, &f);
相比之下,使用atoi和atof更簡潔一些。
1.4.2 rand / srand 隨機數(shù)生成
在抽簽、抽獎等涉及“隨機事件”的應(yīng)用場景中,需要隨機數(shù)生成函數(shù)。由于計算機中并沒有隨機性和偶然性,想制造一個隨機數(shù)實際上是一件比較困難的事情。要真正隨機的數(shù)字,需要購置在非常昂貴的硬件,這些硬件利用某些自然科學(xué)的規(guī)則來生成隨機數(shù),因為價格昂貴,所以專業(yè)的隨機數(shù)生成器只用于專業(yè)用途。我們這里介紹的適用普通PC機能夠勝任的“偽隨機數(shù)”生成函數(shù),能夠生成近似隨機的數(shù)據(jù)就可以了。
rand函數(shù)用于生成隨機數(shù),該函數(shù)返回一個整數(shù)。調(diào)用以下代碼測試一下:
[cpp] view plain copyfor(int i=0; i《10; i++)
{
printf(”%d \n“, rand());
}
控制臺輸出了10個完全沒有規(guī)律的數(shù)字,因為完全無規(guī)律可循,所以稱它為隨機數(shù)。這里為了顯示方便,只生成了10個隨機數(shù),實際上可以改成1000次、10000次試試,會發(fā)現(xiàn)它確實是雜亂無章、完全沒有規(guī)律的出現(xiàn)的。
那么為什么說它是“偽隨機數(shù)”呢?說明它沒有真正的實現(xiàn)“隨機”??梢赃@么驗證一下,把相同的程序反復(fù)運行數(shù)次,會發(fā)現(xiàn)每次程序運行輸出的結(jié)果都是相同的一個序列的數(shù)字。例如,第一次運行程序的時候輸出13435 31833 5075 19863 30565 11677 1339 4096 31105 9088等10個隨機數(shù),當關(guān)閉程序再次運行時,輸出的10個隨機數(shù)還是這10個數(shù)。
為了解決這個問題,stdlib.h里提供了srand函數(shù)。srand函數(shù)用于為程序設(shè)置一個種子(seed),當種子不同時,程序產(chǎn)生的隨機數(shù)序列也不同。srand只需要在程序開始時設(shè)置一次,例如,可以在main()函數(shù)開始時運行一次。種子怎么定呢?要保證程序每次運行時,這個種子的值不同,一般來說是取系統(tǒng)時間來作為種子的。
[cpp] view plain copyvoid main()
{
srand (time (NULL));
。。。 do something else 。。。
}
每次程序運行時,time(NULL)返回的時間是不同的,于是srand每次都是使用了不同的種子。在程序中再調(diào)用rand()來生成隨機數(shù)時,會發(fā)現(xiàn)每次程序運行生成的隨機數(shù)序列是不同的。
在實際應(yīng)用中如何使用rand函數(shù)呢?例如,有一種彩票叫“七星彩”,每次生成的中獎號碼是7個屆于0~9之間的隨機數(shù)字??梢杂胷and來隨機生成一注號碼。
[cpp] view plain copyvoid main()
{
srand (time (NULL));
int code[7]; // 一注號碼為7個數(shù)字
for(int i=0; i《7; i++)
{
int r = rand () % 10; // 取模使每個數(shù)字界于0~9之間
code[i] = r;
}
}
注意,一般都要為rand()生的隨機數(shù)指定一個區(qū)間。
生成[100,120]之間的隨機數(shù),
[cpp] view plain copyint r = 100 + rand () % 20;
生成[0,1]區(qū)間的隨機小數(shù),
double r = rand()/ (double)RAND_MAX;
其中, RAND_MAX在VC下的定義是32767,在其他操作系統(tǒng)下可能是其他值。
例題:給定10個數(shù),要求從中每次隨機選出5個數(shù)。
思路:首先,從10個數(shù)里面隨機挑選出1個數(shù),然后再從剩下的9個數(shù)里挑選1個,然后再從剩下的8個數(shù)里挑選1個。。。。。。
設(shè)計:用一個flags數(shù)組來表示哪個數(shù)已經(jīng)被選中了,例如 flags[3] = 1表示第第4個數(shù)已經(jīng)被選中,flags[9]=0表示第10個數(shù)未被選中。
點擊查看此節(jié)內(nèi)容,第16章
1.4.3 system 調(diào)用系統(tǒng)命令行
用system函數(shù)可以調(diào)用系統(tǒng)命令行,在windows下可以執(zhí)行DOS命令行, 在linux下SHELL命令行。比如,刪除d:\aaa.pdf這個文件,
system (”del /F /Q d:\\aaa.pdf“);
其實原則上并不限于DOS命令,所以的命令行都可以運行的。例如,調(diào)用瀏覽器打開一個網(wǎng)站,
[cpp] view plain copysystem (”explorer http://www.afanihao.cn“);
1.5 string.h
string.h中提供了一系統(tǒng)內(nèi)存操作函數(shù)及字符串操作函數(shù)。在學(xué)習(xí)本節(jié)之前,一定要先學(xué)習(xí)第15章中,掌握字符串的意義。
#include 《string.h》
char *strcat(char *s1, const char *s2); // 拼接字符串
char *strchr(char *s, int c); // 查找字符
int strcmp(const char *s1, const char *s2); // 字符串比較
char *strcpy(char *s1, const char *s2); // 拷貝字符串
char *strstr(char *s1, const char *s2); // 查找子串
size_t strlen(const char *s); // 計算長度
int memcmp(const void *s1, const void *s2, size_t n); //按內(nèi)存比較
void *memcpy(void *s1, const void *s2, size_t n); // 按內(nèi)存拷貝
void *memmove(void *s1, const void *s2, size_t n); // 移動數(shù)據(jù)
void *memset(void *s, int c, size_t n); // 按字節(jié)填充內(nèi)存
1.5.1 strcpy拷貝字符串
strcpy(a,b)用于將字符串b拷貝到目標緩沖區(qū),
如,
[cpp] view plain copychar buf[128];
strcpy(buf, ”LiMing“); // 目標緩沖區(qū)內(nèi)容拷貝為”LiMing“
1.5.2 strcat拼接字符串
strcat(a,b)用于將字符串b拼接于字符串a(chǎn),也就是說把字符串拷貝到目標字符串的末尾。此函數(shù)要求目標緩沖區(qū)足夠大,
[cpp] view plain copy#include 《string.h》
void main()
{
char a [128] = ”hello“;
char b [] = ”world“;
strcat(a, b); // 目標a結(jié)果為”helloworld“
}
1.5.3 strcmp比較字符串
關(guān)于字符串比較的意義在第15章已經(jīng)講述。strcmp(a,b)用于比較字符串,當返回為0時表示完全相等,小于0時表示a《b,大于0時表示a》b。
[cpp] view plain copyint ret = strcmp(”Jack“, ”Jacky“); // 返回值 ret=-1,表示”Jack“《”Jacky“
1.5.4 strlen求字符串長度
字符串求長度時,結(jié)束符‘\0’不計算在內(nèi)。例如,
[cpp] view plain copyint n = strlen(”LiMing“); // 返回長度n為6
1.5.5 strchr查找字符
strchr(s, c)用于在字符串s中查找字符c,并返回第一處匹配的位置。其返回值是char*類型,表示匹配的位置,
[cpp] view plain copychar* s = ”LiMing“;
char* p = strchr(s, ‘M’); // 返回值p指向字符‘M’的地址
if(p!= NULL)
{
printf(”find: %s \n“, p);
}
1.5.6 strstr查找子串
點擊查看此節(jié)內(nèi)容,第16章
1.5.7 memset內(nèi)存填充
[cpp] view plain copymemset(s, c, n)用于向目標緩沖區(qū)s中添加n個相同的字符c,例如,
unsigned char buf[128];
memset(buf, 0, sizeof(buf)); // 全部填充為0
memset(buf, 0xFF, 128); // 全部填充為0xFF
memset(buf, 0x55, 100); // 前100個字節(jié)填充為0x55
memset(buf+100, 0x77, 10); // 100..109填充為0x77
1.5.8 memcpy內(nèi)存拷貝
點擊查看此節(jié)內(nèi)容,第16章
1.5.9 memcmp內(nèi)存比較
點擊查看此節(jié)內(nèi)容,第16章
其中,a和b的比較是容易得出結(jié)果的 ,但a與c的比較呢?由于要轉(zhuǎn)成unsigned char再比較,所以c》a。
1.5.10 memmove移動數(shù)據(jù)
memmove(dst, src, n)用于在內(nèi)存中移動數(shù)據(jù),將開始于src的n個字節(jié)移動到dst位置,這個函數(shù)的強大之處在于它允許src和dst有交迭。
例如,利用這個函數(shù)我們可以實現(xiàn)在字符串中插入字符,當插入字符串如果將插入點之后的所有字符后移,
點擊查看此節(jié)內(nèi)容,第16章
點擊查看此節(jié)內(nèi)容,第16章
1.6 stdarg.h
stdarg.h中的接口用于實現(xiàn)省略號參數(shù)。在函數(shù)中曾經(jīng)指出,函數(shù)參數(shù)有一個特殊形式,就是不指定個數(shù)和類型,直接用省略號表示。
void test (。。。)
當參數(shù)為省略號時,可以輸入任意個數(shù)的參數(shù),而且參數(shù)的類型不受限制。我們觀察printf函數(shù)的原型,發(fā)現(xiàn)它就是使用了省略號參數(shù),
int printf(const char *format, 。。。);
printf的第一個參數(shù)為字符串類型,用于傳遞格式參數(shù), 而后面的省略號處可以傳遞0..N個參數(shù)。這種靈活的傳參方式在某此特殊場合會用到。下面就指出一種應(yīng)用需求并給出其實現(xiàn)方法。
需求:自定義一個函數(shù)用于日志輸出,在輸出的時候自動添加日期時期信息、日志等級,其原型要求是這種形式,
void log(int level, const char* fmt, 。。。);
其中, level為日志級別:level=0時,顯示ERR, level=1時顯示:WRN, level=2時顯示INF, level=3時顯示DBG。
在調(diào)用的時候要求跟printf類似地使用格式化控制符來控制輸出,并前綴以日期顯示:
log(0, “My name is %s, I‘m %d years old.\n”, “Jennifer”, 30);
此需求實際上就是要實現(xiàn)一個自定義的打印函數(shù),
[cpp] view plain copy#include 《stdio.h》
#include 《string.h》
#include 《stdarg.h》
void log(int level, const char* fmt, 。。。)
{
// 打印日志等級
const char* token = ”DBG“;
switch(level)
{
case 0: token = ”ERR“; break;
case 1: token = ”WRN“; break;
case 2: token = ”INF“; break;
case 3: token = ”DBG“; break;
}
printf(”[ %s ] “, token);
// 將省略號參數(shù)格式化成字符串
char buf[512];
va_list args;
va_start(args, fmt);
vsprintf(buf, fmt, args);
va_end(args);
printf(buf);
}
int main()
{
log(2, ”Name: %s, Age: %d.\n“, ”LiMing“, 30);
return 0;
}
對省略號參數(shù)的核心處理是這幾行代碼:
[cpp] view plain copyva_list args;
va_start(args, fmt);
。。。 此處已經(jīng)將參數(shù)取得在args里。。。
va_end(args);
能夠以va_list作為參數(shù)的函數(shù)有vsprintf, vprintf, vfprintf,其原型是:
int vprintf(const char *format, va_list ap); // 輸出到控制臺
int vsprintf(char *s, const char *format, va_list ap); // 輸出到字符串緩沖區(qū)
int vfprintf(FILE *stream, const char *format, va_list ap); // 輸出到文件流
-
函數(shù)庫
+關(guān)注
關(guān)注
1文章
84瀏覽量
32429 -
標準函數(shù)
+關(guān)注
關(guān)注
0文章
3瀏覽量
6346 -
c函數(shù)
+關(guān)注
關(guān)注
0文章
11瀏覽量
7526
發(fā)布評論請先 登錄
相關(guān)推薦
評論