Linux下C語言編程資料

1(Linux程序設計入門——基礎知識Linux下C語言編程基礎知識前言:這篇文章介紹在LINUX下進行C語言編程所需要的基礎知識。在這篇文章當中,我們將會學到以下內容:源程序編譯Makefile的編寫程序庫的鏈接程序的調試頭文件和系統求助

    1.源程序的編譯在Linux下面,如果要編譯一個C語言源程序,我們要使用GNU的gcc編譯器。 下面我們以一個實例來說明如何使用gcc編譯器。

    假設我們有下面一個非常簡單的源程序(hello.c):int main(int argc,char **argv)

    { printf("Hello Linux/n");}要編譯這個程序,我們只要在命令行下執行:gcc -o hello hello.c gcc 編譯器就會爲我們生成一個hello的可執行文件。執行。/hello就可以看到程序的輸出結果了。命令行中 gcc表示我們是用gcc來編譯我們的源程序,-o 選項表示我們要求編譯器給我們輸出的可執行文件名爲hello 而hello.c是我們的源程序文件。

    gcc編譯器有許多選項,一般來說我們只要知道其中的幾個就夠了。 -o選項我們已經知道了,表示我們要求輸出的可執行文件名。 -c選項表示我們只要求編譯器輸出目標代碼,而不必要輸出可執行文件。 -g選項表示我們要求編譯器在編譯的時候提供我們以後對程序進行調試的信息。

    知道了這三個選項,我們就可以編譯我們自己所寫的簡單的源程序了,如果你想要知道更多的選項,可以查看gcc的幫助文檔,那裏有着許多對其它選項的詳細說明。

    2.Makefile的編寫假設我們有下面這樣的一個程序,源代碼如下:/* main.c */ #include "mytool1.h" #include "mytool2.h" int main(int argc,char **argv)

    { mytool1_print("hello");mytool2_print("hello");} /* mytool1.h */ #ifndef _MYTOOL_1_H #define _MYTOOL_1_H void mytool1_print(char *print_str);#endif /* mytool1.c */ #include "mytool1.h" void mytool1_print(char *print_str)

    { printf("This is mytool1 print %s/n",print_str);} /* mytool2.h */ #ifndef _MYTOOL_2_H #define _MYTOOL_2_H void mytool2_print(char *print_str);#endif /* mytool2.c */ #include "mytool2.h" void mytool2_print(char *print_str)

    { printf("This is mytool2 print %s/n",print_str);}當然由於這個程序是很短的我們可以這樣來編譯gcc -c main.c gcc -c mytool1.c gcc -c mytool2.c gcc -o main main.o mytool1.o mytool2.o這樣的話我們也可以產生main程序,而且也不時很麻煩。但是如果我們考慮一下如果有一天我們修改了其中的一個文件(比如說mytool1.c)那麼我們難道還要重新輸入上面的命令?也許你會說,這個很容易解決啊,我寫一個SHELL腳本,讓她幫我去完成不就可以了。是的對於這個程序來說,是可以起到作用的。但是當我們把事情想的更復雜一點,如果我們的程序有幾百個源程序的時候,難道也要編譯器重新一個一個的去編譯?爲此,聰明的程序員們想出了一個很好的工具來做這件事情,這就是make.我們只要執行以下make,就可以把上面的問題解決掉。在我們執行make之前,我們要先編寫一個非常重要的文件。——Makefile.對於上面的那個程序來說,可能的一個Makefile的文件是:# 這是上面那個程序的Makefile文件main:main.o mytool1.o mytool2.o gcc -o main main.o mytool1.o mytool2.o main.o:main.c mytool1.h mytool2.h gcc -c main.c mytool1.o:mytool1.c mytool1.h gcc -c mytool1.c mytool2.o:mytool2.c mytool2.h gcc -c mytool2.c有了這個Makefile文件,不過我們什麼時候修改了源程序當中的什麼文件,我們只要執行make命令,我們的編譯器都只會去編譯和我們修改的文件有關的文件,其它的文件她連理都不想去理的。下面我們學習Makefile是如何編寫的。

    在Makefile中也#開始的行都是註釋行。Makefile中最重要的是描述文件的依賴關係的說明。一般的格式是:target: components TAB rule第一行表示的是依賴關係。第二行是規則。

    比如說我們上面的那個Makefile文件的第二行main:main.o mytool1.o mytool2.o表示我們的目標(target)main的依賴對象(components)是main.o mytool1.o mytool2.o當倚賴的對象在目標修改後修改的話,就要去執行規則一行所指定的命令。就象我們的上面那個Makefile第三行所說的一樣要執行 gcc -o main main.o mytool1.o mytool2.o注意規則一行中的TAB表示那裏是一個TAB鍵Makefile有三個非常有用的變量。分別是$@,$^,$<代表的意義分別是:$@——目標文件,$^——所有的依賴文件,$<——第一個依賴文件。

    如果我們使用上面三個變量,那麼我們可以簡化我們的Makefile文件爲:# 這是簡化後的Makefile main:main.o mytool1.o mytool2.o gcc -o $@ $^ main.o:main.c mytool1.h mytool2.h gcc -c $< mytool1.o:mytool1.c mytool1.h gcc -c $< mytool2.o:mytool2.c mytool2.h gcc -c $<經過簡化後我們的Makefile是簡單了一點,不過人們有時候還想簡單一點。這裏我們學習一個Makefile的缺省規則……c.o:gcc -c $<這個規則表示所有的 .o文件都是依賴與相應的。c文件的。例如mytool.o依賴於mytool.c這樣Makefile還可以變爲:# 這是再一次簡化後的Makefile main:main.o mytool1.o mytool2.o gcc -o $@ $^……c.o:gcc -c $<好了,我們的Makefile 也差不多了,如果想知道更多的關於Makefile規則可以查看相應的文檔。

    3.程序庫的鏈接試着編譯下面這個程序/* temp.c */ #include <math.h> int main(int argc,char **argv)

    { double value;printf("Value:%f/n",value);}這個程序相當簡單,但是當我們用 gcc -o temp temp.c 編譯時會出現下面所示的錯誤。

    /tmp/cc33Kydu.o: In function `main':/tmp/cc33Kydu.o(。text+0xe): undefined reference to `log' collect2: ld returned 1 exit status出現這個錯誤是因爲編譯器找不到log的具體實現。雖然我們包括了正確的頭文件,但是我們在編譯的時候還是要連接確定的庫。在Linux下,爲了使用數學函數,我們必須和數學庫連接,爲此我們要加入 -lm 選項。 gcc -o temp temp.c -lm這樣才能夠正確的編譯。也許有人要問,前面我們用printf函數的時候怎麼沒有連接庫呢?是這樣的,對於一些常用的函數的實現,gcc編譯器會自動去連接一些常用庫,這樣我們就沒有必要自己去指定了。 有時候我們在編譯程序的時候還要指定庫的路徑,這個時候我們要用到編譯器的 -L選項指定路徑。比如說我們有一個庫在 /home/hoyt/mylib下,這樣我們編譯的時候還要加上 -L/h ome/hoyt/mylib.對於一些標準庫來說,我們沒有必要指出路徑。只要它們在起缺省庫的路徑下就可以了。系統的缺省庫的路徑/lib /usr/lib /usr/local/lib 在這三個路徑下面的庫,我們可以不指定路徑。

    還有一個問題,有時候我們使用了某個函數,但是我們不知道庫的名字,這個時候怎麼辦呢?很抱歉,對於這個問題我也不知道答案,我只有一個傻辦法。首先,我到標準庫路徑下面去找看看有沒有和我用的函數相關的庫,我就這樣找到了線程(thread)函數的庫文件(libp thread.a)。 當然,如果找不到,只有一個笨方法。比如我要找sin這個函數所在的庫。 就只好用 nm -o /lib/*.so|grep sin>~/sin 命令,然後看~/sin文件,到那裏面去找了。 在s in文件當中,我會找到這樣的一行libm-2.1.2.so:00009fa0 W sin 這樣我就知道了sin在libm-2.1.2.so庫裏面,我用 -lm選項就可以了(去掉前面的lib和後面的版本標誌,就剩下m了所以是 -lm)。 如果你知道怎麼找,請趕快告訴我,我回非常感激的。謝謝!

    4.程序的調試我們編寫的程序不太可能一次性就會成功的,在我們的程序當中,會出現許許多多我們想不到的錯誤,這個時候我們就要對我們的程序進行調試了。

    最常用的調試軟件是gdb.如果你想在圖形界面下調試程序,那麼你現在可以選擇xxgdb.記得要在編譯的時候加入 -g選項。關於gdb的使用可以看gdb的幫助文件。由於我沒有用過這個軟件,所以我也不能夠說出如何使用。 不過我不喜歡用gdb.跟蹤一個程序是很煩的事情,我一般用在程序當中輸出中間變量的值來調試程序的。當然你可以選擇自己的辦法,沒有必要去學別人的。現在有了許多IDE環境,裏面已經自己帶了調試器了。你可以選擇幾個試一試找出自己喜歡的一個用。

    5.頭文件和系統求助有時候我們只知道一個函數的大概形式,不記得確切的表達式,或者是不記得着函數在那個頭文件進行了說明。這個時候我們可以求助系統。

    比如說我們想知道fread這個函數的確切形式,我們只要執行 man fread 系統就會輸出着函數的詳細解釋的。和這個函數所在的頭文件<stdio.h>說明了。 如果我們要write這個函數的說明,當我們執行man write時,輸出的結果卻不是我們所需要的。 因爲我們要的是w rite這個函數的說明,可是出來的卻是write這個命令的說明。爲了得到write的函數說明我們要用 man 2 write. 2表示我們用的write這個函數是系統調用函數,還有一個我們常用的是3表示函數是C的庫函數。

    記住不管什麼時候,man都是我們的最好助手。

    好了,這一章就講這麼多了,有了這些知識我們就可以進入激動人心的Linux下的C程序探險活動。

    2(Linux程序設計入門——進程介紹Linux下進程的創建前言:這篇文章是用來介紹在Linux下和進程相關的各個概念。我們將會學到:進程的概念進程的身份進程的創建守護進程的創建


    1.進程的概念Linux操作系統是面向多用戶的。在同一時間可以有許多用戶向操作系統發出各種命令。那麼操作系統是怎麼實現多用戶的環境呢? 在現代的操作系統裏面,都有程序和進程的概念。那麼什麼是程序,什麼是進程呢? 通俗的講程序是一個包含可以執行代碼的文件,是一個靜態的文件。而進程是一個開始執行但是還沒有結束的程序的實例。就是可執行文件的具體實現。 一個程序可能有許多進程,而每一個進程又可以有許多子進程。依次循環下去,而產生子孫進程。 當程序被系統調用到內存以後,系統會給程序分配一定的資源(內存,設備等等)然後進行一系列的複雜操作,使程序變成進程以供系統調用。在系統裏面只有進程沒有程序,爲了區分各個不同的進程,系統給每一個進程分配了一個ID(就象我們的身份證)以便識別。 爲了充分的利用資源,系統還對進程區分了不同的狀態。將進程分爲新建,運行,阻塞,就緒和完成五個狀態。 新建表示進程正在被創建,運行是進程正在運行,阻塞是進程正在等待某一個事件發生,就緒是表示系統正在等待CPU來執行命令,而完成表示進程已經結束了系統正在回收資源。 關於進程五個狀態的詳細解說我們可以看《操作系統》上面有詳細的解說。

    2.進程的標誌上面我們知道了進程都有一個ID,那麼我們怎麼得到進程的ID呢?系統調用getpid可以得到進程的ID,而getppid可以得到父進程(創建調用該函數進程的進程)的ID. #include <unistd> pid_t getpid(void);pid_t getppid(void);進程是爲程序服務的,而程序是爲了用戶服務的。系統爲了找到進程的用戶名,還爲進程和用戶建立聯繫。這個用戶稱爲進程的所有者。相應的每一個用戶也有一個用戶ID.通過系統調用getuid可以得到進程的所有者的ID.由於進程要用到一些資源,而Linux對系統資源是進行保護的,爲了獲取一定資源進程還有一個有效用戶ID.這個ID和系統的資源使用有關,涉及到進程的權限。 通過系統調用geteuid我們可以得到進程的有效用戶ID. 和用戶ID相對應進程還有一個組ID和有效組ID系統調用getgid和getegid可以分別得到組ID和有效組ID #include <unistd> #include <sys/types.h>

    uid_t getuid(void);uid_t geteuid(void);gid_t getgid(void);git_t getegid(void);有時候我們還會對用戶的其他信息感興趣(登錄名等等),這個時候我們可以調用getpwui d來得到。

    struct passwd { char *pw_name; /* 登錄名稱 */ char *pw_passwd; /* 登錄口令 */ uid_t pw_uid; /* 用戶ID */ gid_t pw_gid; /* 用戶組ID */ char *pw_gecos; /* 用戶的真名 */ char *pw_dir; /* 用戶的目錄 */ char *pw_shell; /* 用戶的SHELL */ };#include <pwd.h> #include <sys/types.h>

    struct passwd *getpwuid(uid_t uid);下面我們學習一個實例來實踐一下上面我們所學習的幾個函數:#include <unistd.h> #include <pwd.h> #include <sys/types.h> #include <stdio.h> int main(int argc,char **argv)

    { pid_t my_pid,parent_pid;uid_t my_uid,my_euid;gid_t my_gid,my_egid;struct passwd *my_info;my_pid=getpid();parent_pid=getppid();my_uid=getuid();my_euid=geteuid();my_gid=getgid();my_egid=getegid();my_info=getpwuid(my_uid);printf("Process ID:%ld/n",my_pid);printf("Parent ID:%ld/n",parent_pid);printf("User ID:%ld/n",my_uid);printf("Effective User ID:%ld/n",my_euid);printf("Group ID:%ld/n",my_gid);printf("Effective Group ID:%ld/n",my_egid):if(my_info)

    { printf("My Login Name:%s/n" ,my_info->pw_name);printf("My Password :%s/n" ,my_info->pw_passwd);printf("My User ID :%ld/n",my_info->pw_uid);printf("My Group ID :%ld/n",my_info->pw_gid);printf("My Real Name:%s/n" ,my_info->pw_gecos);printf("My Home Dir :%s/n", my_info->pw_dir);printf("My Work Shell:%s/n", my_info->pw_shell);} 3.進程的創建創建一個進程的系統調用很簡單。我們只要調用fork函數就可以了。

    #include <unistd.h>

    pid_t fork();當一個進程調用了fork以後,系統會創建一個子進程。這個子進程和父進程不同的地方只有他的進程ID和父進程ID,其他的都是一樣。就象符進程克隆(clone)自己一樣。當然創建兩個一模一樣的進程是沒有意義的。爲了區分父進程和子進程,我們必須跟蹤fork的返回值。 當fork掉用失敗的時候(內存不足或者是用戶的最大進程數已到)fork返回-1,否則f ork的返回值有重要的作用。對於父進程fork返回子進程的ID,而對於fork子進程返回0.我們就是根據這個返回值來區分父子進程的。 父進程爲什麼要創建子進程呢?前面我們已經說過了Linux是一個多用戶操作系統,在同一時間會有許多的用戶在爭奪系統的資源。有時進程爲了早一點完成任務就創建子進程來爭奪資源。 一旦子進程被創建,父子進程一起從fork處繼續執行,相互競爭系統的資源。有時候我們希望子進程繼續執行,而父進程阻塞直到子進程完成任務。這個時候我們可以調用wait或者waitpid系統調用。

    #include <sys/types.h> #include <sys/wait.h>

    pid_t wait(int *stat_loc);pid_t waitpid(pid_t pid,int *stat_loc,int options);wait系統調用會使父進程阻塞直到一個子進程結束或者是父進程接受到了一個信號。如果沒有父進程沒有子進程或者他的子進程已經結束了wait回立即返回。成功時(因一個子進程結束)wait將返回子進程的ID,否則返回-1,並設置全局變量errno.stat_loc是子進程的退出狀態。子進程調用exit,_exit 或者是return來設置這個值。 爲了得到這個值Linux定義了幾個宏來測試這個返回值。

    WIFEXITED:判斷子進程退出值是非0 WEXITSTATUS:判斷子進程的退出值(當子進程退出時非0)。

    WIFSIGNALED:子進程由於有沒有獲得的信號而退出。

    WTERMSIG:子進程沒有獲得的信號號(在WIFSIGNALED爲真時纔有意義)。

    waitpid等待指定的子進程直到子進程返回。如果pid爲正值則等待指定的進程(pid)。如果爲0則等待任何一個組ID和調用者的組ID相同的進程。爲-1時等同於wait調用。小於-1時等待任何一個組ID等於pid絕對值的進程。 stat_loc和wait的意義一樣。 options可以決定父進程的狀態。可以取兩個值 WNOHANG:父進程立即返回當沒有子進程存在時。 WUNTACHE D:當子進程結束時waitpid返回,但是子進程的退出狀態不可得到。

    父進程創建子進程後,子進程一般要執行不同的程序。爲了調用系統程序,我們可以使用系統調用exec族調用。exec族調用有着5個函數。

    #include <unistd.h> int execl(const char *path,const char *arg,……);int execlp(const char *file,const char *arg,……);int execle(const char *path,const char *arg,……);int execv(const char *path,char *const argv[]);int execvp(const char *file,char *const argv[]):exec族調用可以執行給定程序。關於exec族調用的詳細解說可以參考系統手冊(man exec l)。 下面我們來學習一個實例。注意編譯的時候要加 -lm以便連接數學函數庫。

    #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #include <stdio.h> #include <errno.h> #include <math.h> void main(void)

    { pid_t child;int status;printf("This will demostrate how to get child status/n");if()child=fork()(==-1)

    { printf("Fork Error :%s/n",strerror)errno();exit(1);} else if(child==0)

    { int i;printf("I am the child:%ld/n",getpid)();for(i=0;i<1000000;i++) sin(i);i=5;printf("I exit with %d/n",i);exit(i);} while()(child=wait)&status()==-1(&)errno==EINTR();if(child==-1)

    printf("Wait Error:%s/n",strerror)errno();else if(!status)

    printf("Child %ld terminated normally return status is zero/n",child);else if(WIFEXITED)status()

    printf("Child %ld terminated normally return status is %d/n",child,WEXITSTATUS)status();else if(WIFSIGNALED)status()

    printf("Child %ld terminated due to signal %d znot caught/n",child,WTERMSIG)status();{ strerror函數會返回一個指定的錯誤號的錯誤信息的字符串。

    4.守護進程的創建如果你在DOS時代編寫過程序,那麼你也許知道在DOS下爲了編寫一個常駐內存的程序我們要編寫多少代碼了。相反如果在Linux下編寫一個"常駐內存"的程序卻是很容易的。我們只要幾行代碼就可以做到。 實際上由於Linux是多任務操作系統,我們就是不編寫代碼也可以把一個程序放到後臺去執行的。我們只要在命令後面加上&符號SHELL就會把我們的程序放到後臺去運行的。 這裏我們"開發"一個後臺檢查郵件的程序。這個程序每個一個指定的時間回去檢查我們的郵箱,如果發現我們有郵件了,會不斷的報警(通過機箱上的小喇叭來發出聲音)。 後面有這個函數的加強版本加強版本後臺進程的創建思想: 首先父進程創建一個子進程。然後子進程殺死父進程(是不是很無情?)。 信號處理所有的工作由子進程來處理。

    #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <errno.h> #include <fcntl.h> #include <signal.h> /* Linux 的默任個人的郵箱地址是 /var/spool/mail/用戶的登錄名 */ #define MAIL "/var/spool/mail/hoyt" /* 睡眠10秒鐘 */

    #define SLEEP_TIME 10 main(void)

    { pid_t child;if()child=fork()(==-1)

    { printf("Fork Error:%s/n",strerror)errno();exit(1);} else if(child>0)

    while(1);if(kill)getppid(),SIGTERM(==-1)

    { printf("Kill Parent Error:%s/n",strerror)errno();exit(1);} { int mailfd;while(1)

    { if()mailfd=open(MAIL,O_RDONLY)(!=-1)

    { fprintf(stderr,"%s","7");close(mailfd);} sleep(SLEEP_TIME);{你可以在默認的路徑下創建你的郵箱文件,然後測試一下這個程序。當然這個程序還有很多地方要改善的。我們後面會對這個小程序改善的,再看我的改善之前你可以嘗試自己改善一下。比如讓用戶指定郵相的路徑和睡眠時間等等。相信自己可以做到的。動手吧,勇敢的探險者。

    好了進程一節的內容我們就先學到這裏了。進程是一個非常重要的概念,許多的程序都會用子進程。創建一個子進程是每一個程序員的基本要求!

    3(Linux程序設計入門——文件操作Linux下文件的操作前言:我們在這一節將要討論linux下文件操作的各個函數。

    文件的創建和讀寫文件的各個屬性目錄文件的操作管道文件

----------------------------------------------------------------------------
----

    1.文件的創建和讀寫我假設你已經知道了標準級的文件操作的各個函數(fopen,fread,fwrite等等)。當然如果你不清楚的話也不要着急。我們討論的系統級的文件操作實際上是爲標準級文件操作服務的。

    當我們需要打開一個文件進行讀寫操作的時候,我們可以使用系統調用函數open.使用完成以後我們調用另外一個close函數進行關閉操作。

    #include <fcntl.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h>

    int open(const char *pathname,int flags);int open(const char *pathname,int flags,mode_t mode);int close(int fd);open函數有兩個形式。其中pathname是我們要打開的文件名(包含路徑名稱,缺省是認爲在當前路徑下面)。flags可以去下面的一個值或者是幾個值的組合。

    O_RDONLY:以只讀的方式打開文件。

    O_WRONLY:以只寫的方式打開文件。

    O_RDWR:以讀寫的方式打開文件。

    O_APPEND:以追加的方式打開文件。

    O_CREAT:創建一個文件。

    O_EXEC:如果使用了O_CREAT而且文件已經存在,就會發生一個錯誤。

    O_NOBLOCK:以非阻塞的方式打開一個文件。

    O_TRUNC:如果文件已經存在,則刪除文件的內容。

    前面三個標誌只能使用任意的一個。如果使用了O_CREATE標誌,那麼我們要使用open的第二種形式。還要指定mode標誌,用來表示文件的訪問權限。mode可以是以下情況的組合。

-----------------------------------------------------------------

    S_IRUSR 用戶可以讀 S_IWUSR 用戶可以寫S_IXUSR 用戶可以執行 S_IRWXU 用戶可以讀寫執行

-----------------------------------------------------------------

    S_IRGRP 組可以讀 S_IWGRP 組可以寫S_IXGRP 組可以執行 S_IRWXG 組可以讀寫執行

-----------------------------------------------------------------

    S_IROTH 其他人可以讀 S_IWOTH 其他人可以寫S_IXOTH 其他人可以執行 S_IRWXO 其他人可以讀寫執行

-----------------------------------------------------------------

    S_ISUID 設置用戶執行ID S_ISGID 設置組的執行ID

-----------------------------------------------------------------

    我們也可以用數字來代表各個位的標誌。Linux總共用5個數字來表示文件的各種權限。

    00000.第一位表示設置用戶ID.第二位表示設置組ID,第三位表示用戶自己的權限位,第四位表示組的權限,最後一位表示其他人的權限。

    每個數字可以取1(執行權限),2(寫權限),4(讀權限),0(什麼也沒有)或者是這幾個值的和……

    比如我們要創建一個用戶讀寫執行,組沒有權限,其他人讀執行的文件。設置用戶ID位那麼我們可以使用的模式是——1(設置用戶ID)0(組沒有設置)7(1+2+4)0(沒有權限,使用缺省)

    5(1+4)即10705:open("temp",O_CREAT,10705);如果我們打開文件成功,open會返回一個文件描述符。我們以後對文件的所有操作就可以對這個文件描述符進行操作了。

    當我們操作完成以後,我們要關閉文件了,只要調用close就可以了,其中fd是我們要關閉的文件描述符。

    文件打開了以後,我們就要對文件進行讀寫了。我們可以調用函數read和write進行文件的讀寫。

    #include <unistd.h> ssize_t read(int fd, void *buffer,size_t count);ssize_t write(int fd, const void *buffer,size_t count);fd是我們要進行讀寫操作的文件描述符,buffer是我們要寫入文件內容或讀出文件內容的內存地址。count是我們要讀寫的字節數。

    對於普通的文件read從指定的文件(fd)中讀取count字節到buffer緩衝區中(記住我們必須提供一個足夠大的緩衝區),同時返回count.如果read讀到了文件的結尾或者被一個信號所中斷,返回值會小於count.如果是由信號中斷引起返回,而且沒有返回數據,read會返回-1,且設置errno爲EINTR.當程序讀到了文件結尾的時候,read會返回0. write從buffer中寫count字節到文件fd中,成功時返回實際所寫的字節數。

    下面我們學習一個實例,這個實例用來拷貝文件。

    #include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <errno.h> #include <string.h> #define BUFFER_SIZE 1024 int main(int argc,char **argv)

    { int from_fd,to_fd;int bytes_read,bytes_write;char buffer[BUFFER_SIZE];char *ptr;if(argc!=3)

    { fprintf(stderr,"Usage:%s fromfile tofile/n/a",argv[0]);exit(1);} /* 打開源文件 */ if()from_fd=open(argv[1],O_RDONLY)(==-1)

    { fprintf(stderr,"Open %s Error:%s/n",argv[1],strerror)errno();exit(1);} /* 創建目的文件 */ if()to_fd=open(argv[2],O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR)(==-1)

    { fprintf(stderr,"Open %s Error:%s/n",argv[2],strerror)errno();exit(1);} /* 以下代碼是一個經典的拷貝文件的代碼 */ while(bytes_read=read)from_fd,buffer,BUFFER_SIZE()

    { /* 一個致命的錯誤發生了 */ if()bytes_read==-1(&&)errno!=EINTR() break;else if(bytes_read>0)

    { ptr=buffer;while(bytes_write=write)to_fd,ptr,bytes_read()

    { /* 一個致命錯誤發生了 */ if()bytes_write==-1(&&)errno!=EINTR()break;/* 寫完了所有讀的字節 */ else if(bytes_write==bytes_read) break;/* 只寫了一部分,繼續寫 */ else if(bytes_write>0)

    { ptr+=bytes_write;bytes_read-=bytes_write;} /* 寫的時候發生的致命錯誤 */ if(bytes_write==-1)break;{ close(from_fd);close(to_fd);exit(0);} 2.文件的各個屬性文件具有各種各樣的屬性,除了我們上面所知道的文件權限以外,文件還有創建時間,大小等等屬性。

    有時侯我們要判斷文件是否可以進行某種操作(讀,寫等等)。這個時候我們可以使用acce ss函數。

    #include <unistd.h>

    int access(const char *pathname,int mode);pathname:是文件名稱,mode是我們要判斷的屬性。可以取以下值或者是他們的組合。

    R_OK文件可以讀,W_OK文件可以寫,X_OK文件可以執行,F_OK文件存在。當我們測試成功時,函數返回0,否則如果有一個條件不符時,返回-1.如果我們要獲得文件的其他屬性,我們可以使用函數stat或者fstat. #include <sys/stat.h> #include <unistd.h> int stat(const char *file_name,struct stat *buf);int fstat(int filedes,struct stat *buf);struct stat { dev_t st_dev; /* 設備 */ ino_t st_ino; /* 節點 */ mode_t st_mode; /* 模式 */ nlink_t st_nlink; /* 硬連接 */ uid_t st_uid; /* 用戶ID */ gid_t st_gid; /* 組ID */ dev_t st_rdev; /* 設備類型 */ off_t st_off; /* 文件字節數 */ unsigned long st_blksize; /* 塊大小 */ unsigned long st_blocks; /* 塊數 */ time_t st_atime; /* 最後一次訪問時間 */ time_t st_mtime; /* 最後一次修改時間 */ time_t st_ctime; /* 最後一次改變時間(指屬性) */ };stat用來判斷沒有打開的文件,而fstat用來判斷打開的文件。我們使用最多的屬性是st_ mode.通過着屬性我們可以判斷給定的文件是一個普通文件還是一個目錄,連接等等。可以使用下面幾個宏來判斷。

    S_ISLNK(st_mode):是否是一個連接。S_ISREG是否是一個常規文件。S_ISDIR是否是一個目錄S_ISCHR是否是一個字符設備。S_ISBLK是否是一個塊設備S_ISFIFO是否 是一個FIFO文件。S_ISSOCK是否是一個SOCKET文件。 我們會在下面說明如何使用這幾個宏的。

    3.目錄文件的操作在我們編寫程序的時候,有時候會要得到我們當前的工作路徑。C庫函數提供了get cwd來解決這個問題。

    #include <unistd.h>

    char *getcwd(char *buffer,size_t size);我們提供一個size大小的buffer,getcwd會把我們當前的路徑考到buffer中。如果buffer太小,函數會返回-1和一個錯誤號。

    Linux提供了大量的目錄操作函數,我們學習幾個比較簡單和常用的函數。

    #include <dirent.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> int mkdir(const char *path,mode_t mode);DIR *opendir(const char *path);struct dirent *readdir(DIR *dir);void rewinddir(DIR *dir);off_t telldir(DIR *dir);void seekdir(DIR *dir,off_t off);int closedir(DIR *dir);struct dirent { long d_ino;off_t d_off;unsigned short d_reclen;char d_name[NAME_MAX+1]; /* 文件名稱 */ mkdir很容易就是我們創建一個目錄,opendir打開一個目錄爲以後讀做準備。readdir讀一個打開的目錄。rewinddir是用來重讀目錄的和我們學的rewind函數一樣。closedir是關閉一個目錄。telldir和seekdir類似與ftee和fseek函數。

    下面我們開發一個小程序,這個程序有一個參數。如果這個參數是一個文件名,我們輸出這個文件的大小和最後修改的時間,如果是一個目錄我們輸出這個目錄下所有文件的大小和修改時間。

    #include <unistd.h> #include <stdio.h> #include <errno.h> #include <sys/types.h> #include <sys/stat.h> #include <dirent.h> #include <time.h> static int get_file_size_time(const char *filename)

    { struct stat statbuf;if(stat)filename,&statbuf(==-1)

    { printf("Get stat on %s Error:%s/n",filename,strerror)errno();return(-1);} if(S_ISDIR)statbuf.st_mode()return(1);if(S_ISREG)statbuf.st_mode()

    printf("%s size:%ld bytes/tmodified at %s",filename,statbuf.st_size,ctime)&statbuf.st_mtime();

    return(0);{ int main(int argc,char **argv)

    { DIR *dirp;struct dirent *direntp;int stats;if(argc!=2)

    { printf("Usage:%s filename/n/a",argv[0]);exit(1);} if()(stats=get_file_size_time)argv[1]()==0(||)stats==-1()exit(1);if()dirp=opendir(argv[1])(==NULL)

    { printf("Open Directory %s Error:%s/n",argv[1],strerror)errno();exit(1);} while()direntp=readdir(dirp)(!=NULL)

    if(get_file_size_time)direntp-<d_name(==-1)break;closedir(dirp);exit(1);{ 4.管道文件Linux提供了許多的過濾和重定向程序,比如more cat等等。還提供了< > | <<等等重定向操作符。在這些過濾和重 定向程序當中,都用到了管道這種特殊的文件。系統調用pipe可以創建一個管道。

    #include<unistd.h>

    int pipe(int fildes[2]);pipe調用可以創建一個管道(通信緩衝區)。當調用成功時,我們可以訪問文件描述符fild es[0],fildes[1].其中fildes[0]是用來讀的文件描述符,而fildes[1]是用來寫的文件描述符。

    在實際使用中我們是通過創建一個子進程,然後一個進程寫,一個進程讀來使用的。

    關於進程通信的詳細情況請查看進程通信#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/wait.h> #define BUFFER 255 int main(int argc,char **argv)

    { char buffer[BUFFER+1];int fd[2];if(argc!=2)

    { fprintf(stderr,"Usage:%s string/n/a",argv[0]);exit(1);} if(pipe)fd(!=0)

    { fprintf(stderr,"Pipe Error:%s/n/a",strerror)errno();exit(1);} if(fork)(==0)

    { close(fd[0]);printf("Child[%d] Write to pipe/n/a",getpid)();snprintf(buffer,BUFFER,"%s",argv[1]);write(fd[1],buffer,strlen)buffer();printf("Child[%d] Quit/n/a",getpid)();exit(0);} else { close(fd[1]);printf("Parent[%d] Read from pipe/n/a",getpid)();memset(buffer,'',BUFFER+1);read(fd[0],buffer,BUFFER);printf("Parent[%d] Read:%s/n",getpid)(,buffer);exit(1);}爲了實現重定向操作,我們需要調用另外一個函數dup2. #include <unistd.h>

    int dup2(int oldfd,int newfd);dup2將用oldfd文件描述符來代替newfd文件描述符,同時關閉newfd文件描述符。也就是說,所有向newfd操作都轉到oldfd上面。下面我們學習一個例子,這個例子將標準輸出重定向到一個文件。

    #include <unistd.h> #include <stdio.h> #include <errno.h> #include <fcntl.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #define BUFFER_SIZE 1024 int main(int argc,char **argv)

    { int fd;char buffer[BUFFER_SIZE];if(argc!=2)

    { fprintf(stderr,"Usage:%s outfilename/n/a",argv[0]);exit(1);} if()fd=open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR)(==-1)

    { fprintf(stderr,"Open %s Error:%s/n/a",argv[1],strerror)errno();exit(1);} if(dup2)fd,STDOUT_FILENO(==-1)

    { fprintf(stderr,"Redirect Standard Out Error:%s/n/a",strerror)errno();exit(1);} fprintf(stderr,"Now,please input string");fprintf(stderr,")To quit use CTRL+D(/n");while(1)

    { fgets(buffer,BUFFER_SIZE,stdin);if(feof)stdin()break;write(STDOUT_FILENO,buffer,strlen)buffer();} exit(0);{好了,文件一章我們就暫時先討論到這裏,學習好了文件的操作我們其實已經可以寫出一些比較有用的程序了。我們可以編寫一個實現例如dir,mkdir,cp,mv等等常用的文件操作命令了。

    想不想自己寫幾個試一試呢?

    4(程序設計入門——時間概念前言:Linux下的時間概念這一章我們學習Linux的時間表示和計算函數時間的表示時間的測量計時器的使用1.時間表示 在程序當中,我們經常要輸出系統當前的時間,比如我們使用date命令的輸出結果。這個時候我們可以使用下面兩個函數#include <time.h>

    time_t time(time_t *tloc);char *ctime(const time_t *clock);time函數返回從1970年1月1日0點以來的秒數。存儲在time_t結構之中。不過這個函數的返回值對於我們來說沒有什麼實際意義。這個時候我們使用第二個函數將秒數轉化爲字符串…… 這個函數的返回類型是固定的:一個可能值爲。 Thu Dec 7 14:58:59 2000 這個字符串的長度是固定的爲26 2.時間的測量 有時候我們要計算程序執行的時間。比如我們要對算法進行時間分析……這個時候可以使用下面這個函數。

    #include <sys/time.h>

    int gettimeofday(struct timeval *tv,struct timezone *tz);strut timeval { long tv_sec; /* 秒數 */ long tv_usec; /* 微秒數 */ };gettimeofday將時間保存在結構tv之中。tz一般我們使用NULL來代替。

    #include <sys/time.h< #include <stdio.h< #include <math.h< void function()

    { unsigned int i,j;double y;for(i=0;i<1000;i++)

    for(j=0;j<1000;j++)

    y=sin()double(i);{ main()

    { struct timeval tpstart,tpend;float timeuse;gettimeofday(&tpstart,NULL);function();gettimeofday(&tpend,NULL);timeuse=1000000*(tpend.tv_sec-tpstart.tv_sec)+ tpend.tv_usec-tpstart.tv_usec;timeuse/=1000000;printf("Used Time:%f/n",timeuse);exit(0);}這個程序輸出函數的執行時間,我們可以使用這個來進行系統性能的測試,或者是函數算法的效率分析。在我機器上的一個輸出結果是: Used Time:0.556070 3.計時器的使用 Linux操作系統爲每一個進程提供了3個內部間隔計時器。

    ITIMER_REAL:減少實際時間。到時的時候發出SIGALRM信號。

    ITIMER_VIRTUAL:減少有效時間(進程執行的時間)。產生SIGVTALRM信號。

    ITIMER_PROF:減少進程的有效時間和系統時間(爲進程調度用的時間)。這個經常和上面一個使用用來計算系統內核時間和用戶時間。產生SIGPROF信號。

    具體的操作函數是:#include <sys/time.h> int getitimer(int which,struct itimerval *value);int setitimer(int which,struct itimerval *newval,struct itimerval *oldval);struct itimerval { struct timeval it_interval;struct timeval it_value;} getitimer函數得到間隔計時器的時間值。保存在value中 setitimer函數設置間隔計時器的時間值爲newval.並將舊值保存在oldval中。 which表示使用三個計時器中的哪一個。

    itimerval結構中的it_value是減少的時間,當這個值爲0的時候就發出相應的信號了。 然後設置爲it_interval值。

    #include <sys/time.h> #include <stdio.h> #include <unistd.h> #include <signal.h> #include <string.h> #define PROMPT "時間已經過去了兩秒鐘/n/a" char *prompt=PROMPT;unsigned int len;void prompt_info(int signo)

    { write(STDERR_FILENO,prompt,len);} void init_sigaction(void)

    { struct sigaction act;act.sa_handler=prompt_info;act.sa_flags=0;sigemptyset(&act.sa_mask);sigaction(SIGPROF,&act,NULL);} void init_time()

    { struct itimerval value;value.it_value.tv_sec=2;value.it_value.tv_usec=0;value.it_interval=value.it_value;setitimer(ITIMER_PROF,&amp;value,NULL);} int main()

    { len=strlen(prompt);init_sigaction();init_time();while(1);exit(0);}這個程序每執行兩秒中之後會輸出一個提示。

    5(Linux程序設計入門——信號處理Linux下的信號事件前言:這一章我們討論一下Linux下的信號處理函數。

    Linux下的信號處理函數:信號的產生信號的處理其它信號函數一個實例1.信號的產生Linux下的信號可以類比於DOS下的INT或者是Windows下的事件。在有一個信號發生時候相信的信號就會發送給相應的進程。在Linux下的信號有以下幾個。 我們使用 kill -l命令可以得到以下的輸出結果:1( SIGHUP 2) SIGINT 3( SIGQUIT 4) SIGILL 5( SIGTRAP 6) SIGABRT 7( SIGBUS 8) SIGFPE 9( SIGKILL 10) SIGUSR1 11( SIGSEGV 12) SIGUSR2 13( SIGPIPE 14) SIGALRM 15( SIGTERM 17) SIGCHLD 18( SIGCONT 19) SIGSTOP 20( SIGTSTP 21) SIGTTIN 22( SIGTTOU 23) SIGURG 24( SIGXCPU 25) SIGXFSZ 26( SIGVTALRM 27) SIGPROF 28( SIGWINCH 29) SIGIO 30( SIGPWR關於這些信號的詳細解釋請查看man 7 signal的輸出結果。 信號事件的發生有兩個來源:一個是硬件的原因)比如我們按下了鍵盤(,一個是軟件的原因)比如我們使用系統函數或者是命令發出信號(。 最常用的四個發出信號的系統函數是kill, raise, alarm和setit imer函數。 setitimer函數我們在計時器的使用 那一章再學習。

    #include <sys/types.h> #include <signal.h> #include <unistd.h> int kill(pid_t pid,int sig);int raise(int sig);unisigned int alarm(unsigned int seconds);kill系統調用負責向進程發送信號sig.如果pid是正數,那麼向信號sig被髮送到進程pid.如果pid等於0,那麼信號sig被髮送到所以和pid進程在同一個進程組的進程如果pid等於-1,那麼信號發給所有的進程表中的進程,除了最大的哪個進程號。

    如果pid由於-1,和0一樣,只是發送進程組是-pid.我們用最多的是第一個情況。還記得我們在守護進程那一節的例子嗎?我們那個時候用這個函數殺死了父進程守護進程的創建raise系統調用向自己發送一個sig信號。我們可以用上面那個函數來實現這個功能的。

    alarm函數和時間有點關係了,這個函數可以在seconds秒後向自己發送一個SIGALRM信號…… 下面這個函數會有什麼結果呢?

    #include <unistd.h> main()

    { unsigned int i;alarm(1);for(i=0;1;i++)

    printf("I=%d",i);{ SIGALRM的缺省操作是結束進程,所以程序在1秒之後結束,你可以看看你的最後I值爲多少,來比較一下大家的系統性能差異(我的是2232)。

    2.信號操作 有時候我們希望進程正確的執行,而不想進程受到信號的影響,比如我們希望上面那個程序在1秒鐘之後不結束。這個時候我們就要進行信號的操作了。

    信號操作最常用的方法是信號屏蔽。信號屏蔽要用到下面的幾個函數。

    #include <signal.h> int sigemptyset(sigset_t *set);int sigfillset(sigset_t *set);int sigaddset(sigset_t *set,int signo);int sigdelset(sigset_t *set,int signo);int sigismember(sigset_t *set,int signo);int sigprocmask(int how,const sigset_t *set,sigset_t *oset);sigemptyset函數初始化信號集合set,將set設置爲空。sigfillset也初始化信號集合,只是將信號集合設置爲所有信號的集合。sigaddset將信號signo加入到信號集合之中,sigd elset將信號從信號集合中刪除。sigismember查詢信號是否在信號集合之中。

    sigprocmask是最爲關鍵的一個函數。在使用之前要先設置好信號集合set.這個函數的作用是將指定的信號集合set加入到進程的信號阻塞集合之中去,如果提供了oset那麼當前的進程信號阻塞集合將會保存在oset裏面。參數how決定函數的操作方式。

    SIG_BLOCK:增加一個信號集合到當前進程的阻塞集合之中。

    SIG_UNBLOCK:從當前的阻塞集合之中刪除一個信號集合。

    SIG_SETMASK:將當前的信號集合設置爲信號阻塞集合。

    以一個實例來解釋使用這幾個函數。

    #include <signal.h> #include <stdio.h> #include <math.h> #include <stdlib.h> int main(int argc,char **argv)

    { double y;sigset_t intmask;int i,repeat_factor;if(argc!=2)

    { fprintf(stderr,"Usage:%s repeat_factor/n/a",argv[0]);exit(1);} if()repeat_factor=atoi(argv[1])(<1)repeat_factor=10;sigemptyset(&intmask); /* 將信號集合設置爲空 */ sigaddset(&intmask,SIGINT); /* 加入中斷 Ctrl+C 信號*/ while(1)

    { /*阻塞信號,我們不希望保存原來的集合所以參數爲NULL*/ sigprocmask(SIG_BLOCK,&intmask,NULL);fprintf(stderr,"SIGINT signal blocked/n");for(i=0;i<repeat_factor;i++)y=sin()double(i);fprintf(stderr,"Blocked calculation is finished/n");/* 取消阻塞 */ sigprocmask(SIG_UNBLOCK,&intmask,NULL);fprintf(stderr,"SIGINT signal unblocked/n");for(i=0;i<repeat_factor;i++)y=sin()double(i);fprintf(stderr,"Unblocked calculation is finished/n");} exit(0);{程序在運行的時候我們要使用Ctrl+C來結束。如果我們在第一計算的時候發出SIGINT信號,由於信號已經屏蔽了,所以程序沒有反映。只有到信號被取消阻塞的時候程序纔會結束。

    注意我們只要發出一次SIGINT信號就可以了,因爲信號屏蔽只是將信號加入到信號阻塞集合之中,並沒有丟棄這個信號。一旦信號屏蔽取消了,這個信號就會發生作用。

    有時候我們希望對信號作出及時的反映的,比如當擁護按下Ctrl+C時,我們不想什麼事情也不做,我們想告訴用戶你的這個操作不好,請不要重試,而不是什麼反映也沒有的。 這個時候我們要用到sigaction函數。

    #include <signal.h>

    int sigaction(int signo,const struct sigaction *act,struct sigaction *oact);struct sigaction { void (*sa_handler)(int signo);void (*sa_sigaction)(int siginfo_t *info,void *act);sigset_t sa_mask;int sa_flags;void (*sa_restore)(void);}這個函數和結構看起來是不是有點恐怖呢。不要被這個嚇着了,其實這個函數的使用相當簡單的。我們先解釋一下各個參數的含義。 signo很簡單就是我們要處理的信號了,可以是任何的合法的信號。有兩個信號不能夠使用(SIGKILL和SIGSTOP)。 act包含我們要對這個信號進行如何處理的信息。oact更簡單了就是以前對這個函數的處理信息了,主要用來保存信息的,一般用NULL就OK了。

    信號結構有點複雜。不要緊我們慢慢的學習。

    sa_handler是一個函數型指針,這個指針指向一個函數,這個函數有一個參數。這個函數就是我們要進行的信號操作的函數。 sa_sigaction,sa_restore和sa_handler差不多的,只是參數不同罷了。這兩個元素我們很少使用,就不管了。

    sa_flags用來設置信號操作的各個情況。一般設置爲0好了。sa_mask我們已經學習過了在使用的時候我們用sa_handler指向我們的一個信號操作函數,就可以了。sa_handler有兩個特殊的值:SIG_DEL和SIG_IGN.SIG_DEL是使用缺省的信號操作函數,而SIG_IGN是使用忽略該信號的操作函數。

    這個函數複雜,我們使用一個實例來說明。下面這個函數可以捕捉用戶的CTRL+C信號。並輸出一個提示語句。

    #include <signal.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <unistd.h> #define PROMPT "你想終止程序嗎?" char *prompt=PROMPT;void ctrl_c_op(int signo)

    { write(STDERR_FILENO,prompt,strlen)prompt();} int main()

    { struct sigaction act;act.sa_handler=ctrl_c_op;sigemptyset(&act.sa_mask);act.sa_flags=0;if(sigaction)SIGINT,&act,NULL(<0)

    { fprintf(stderr,"Install Signal Action Error:%s/n/a",strerror)errno();exit(1);} while(1);{在上面程序的信號操作函數之中,我們使用了write函數而沒有使用fprintf函數。是因爲我們要考慮到下面這種情況。如果我們在信號操作的時候又有一個信號發生,那麼程序該如何運行呢? 爲了處理在信號處理函數運行的時候信號的發生,我們需要設置sa_mask成員。 我們將我們要屏蔽的信號添加到sa_mask結構當中去,這樣這些函數在信號處理的時候就會被屏蔽掉的。

    3.其它信號函數 由於信號的操作和處理比較複雜,我們再介紹幾個信號操作函數。

    #include <unistd.h> #include <signal.h> int pause(void);int sigsuspend(const sigset_t *sigmask);pause函數很簡單,就是掛起進程直到一個信號發生了。而sigsuspend也是掛起進程只是在調用的時候用sigmask取代當前的信號阻塞集合。

    #include <sigsetjmp> int sigsetjmp(sigjmp_buf env,int val);void siglongjmp(sigjmp_buf env,int val);還記得goto函數或者是setjmp和longjmp函數嗎。這兩個信號跳轉函數也可以實現程序的跳轉讓我們可以從函數之中跳轉到我們需要的地方。

    由於上面幾個函數,我們很少遇到,所以只是說明了一下,詳細情況請查看聯機幫助。

    4.一個實例 還記得我們在守護進程創建的哪個程序嗎?守護進程在這裏我們把那個程序加強一下。 下面這個程序會在也可以檢查用戶的郵件。不過提供了一個開關,如果用戶不想程序提示有新的郵件到來,可以向程序發送SIGUSR2信號,如果想程序提供提示可以發送SIGUSR1信號。

    #include <unistd.h> #include <stdio.h> #include <errno.h> #include <fcntl.h> #include <signal.h> #include <string.h> #include <pwd.h> #include <sys/types.h> #include <sys/stat.h> /* Linux 的默任個人的郵箱地址是 /var/spool/mail/ */ #define MAIL_DIR "/var/spool/mail/" /* 睡眠10秒鐘 */ #define SLEEP_TIME 10 #define MAX_FILENAME 255 unsigned char notifyflag=1;long get_file_size(const char *filename)

    { struct stat buf;if(stat)filename,&;buf(==-1)

    { if(errno==ENOENT)return 0;else return -1;} return (long)buf.st_size;{ void send_mail_notify(void)

    { fprintf(stderr,"New mail has arrived7/n");} void turn_on_notify(int signo)

    { notifyflag=1;} void turn_off_notify(int signo)

    { notifyflag=0;} int check_mail(const char *filename)

    { long old_mail_size,new_mail_size;sigset_t blockset,emptyset;sigemptyset(&;blockset);sigemptyset(&;emptyset);sigaddset(&;blockset,SIGUSR1);sigaddset(&;blockset,SIGUSR2);old_mail_size=get_file_size(filename);if(old_mail_size&amp;lt;0)return 1;if(old_mail_size>0) send_mail_notify();sleep(SLEEP_TIME);while(1)

    { if(sigprocmask)SIG_BLOCK,&;blockset,NULL(<0) return 1;while(notifyflag==0)sigsuspend(&amp;;emptyset);if(sigprocmask)SIG_SETMASK,&;emptyset,NULL(<0) return 1;new_mail_size=get_file_size(filename);if(new_mail_size>old_mail_size)send_mail_notify;old_mail_size=new_mail_size;sleep(SLEEP_TIME);} int main(void)

    { char mailfile[MAX_FILENAME];struct sigaction newact;struct passwd *pw;if()pw=getpwuid(getuid)()(==NULL)

    { fprintf(stderr,"Get Login Name Error:%s/n/a",strerror)errno();exit(1);} strcpy(mailfile,MAIL_DIR);strcat(mailfile,pw->pw_name);newact.sa_handler=turn_on_notify;newact.sa_flags=0;sigemptyset(&amp;amp;;newact.sa_mask);sigaddset(&;newact.sa_mask,SIGUSR1);sigaddset(&;newact.sa_mask,SIGUSR2);if(sigaction)SIGUSR1,&;newact,NULL(<0)

    fprintf(stderr,"Turn On Error:%s/n/a",strerror)errno();newact.sa_handler=turn_off_notify;if(sigaction)SIGUSR1,&amp;;newact,NULL(<0)

    fprintf(stderr,"Turn Off Error:%s/n/a",strerror)errno();check_mail(mailfile);exit(0);{信號操作是一件非常複雜的事情,比我們想象之中的複雜程度還要複雜,如果你想徹底的弄清楚信號操作的各個問題,那麼除了大量的練習以外還要多看聯機手冊。不過如果我們只是一般的使用的話,有了上面的幾個函數也就差不多了。 我們就介紹到這裏了。

    6(Linux程序設計入門——消息管理前言:Linux下的進程通信)IPC(

    Linux下的進程通信(IPC)

    POSIX無名信號量System V信號量System V消息隊列System V共享內存1.POSIX無名信號量 如果你學習過操作系統,那麼肯定熟悉PV操作了。PV操作是原子操作。也就是操作是不可以中斷的,在一定的時間內,只能夠有一個進程的代碼在CPU上面執行。在系統當中,有時候爲了順利的使用和保護共享資源,大家提出了信號的概念。 假設我們要使用一臺打印機,如果在同一時刻有兩個進程在向打印機輸出,那麼最終的結果會是什麼呢。爲了處理這種情況,POSIX標準提出了有名信號量和無名信號量的概念,由於Li nux只實現了無名信號量,我們在這裏就只是介紹無名信號量了。 信號量的使用主要是用來保護共享資源,使的資源在一個時刻只有一個進程所擁有。爲此我們可以使用一個信號燈。當信號燈的值爲某個值的時候,就表明此時資源不可以使用。否則就表>示可以使用。

    爲了提供效率,系統提供了下面幾個函數POSIX的無名信號量的函數有以下幾個:#include <semaphore.h> int sem_init(sem_t *sem,int pshared,unsigned int value);int sem_destroy(sem_t *sem);int sem_wait(sem_t *sem);int sem_trywait(sem_t *sem);int sem_post(sem_t *sem);int sem_getvalue(sem_t *sem);sem_init創建一個信號燈,並初始化其值爲value.pshared決定了信號量能否在幾個進程間共享。由於目前Linux還沒有實現進程間共享信號燈,所以這個值只能夠取0. sem_dest roy是用來刪除信號燈的。sem_wait調用將阻塞進程,直到信號燈的值大於0.這個函數返回的時候自動的將信號燈的值的件一。sem_post和sem_wait相反,是將信號燈的內容加一同時發出信號喚醒等待的進程……sem_trywait和sem_wait相同,不過不阻塞的,當信號燈的值爲0的時候返回EAGAIN,表示以後重試。sem_getvalue得到信號燈的值。

    由於Linux不支持,我們沒有辦法用源程序解釋了。

    這幾個函數的使用相當簡單的。比如我們有一個程序要向一個系統打印機打印兩頁。我們首先創建一個信號燈,並使其初始值爲1,表示我們有一個資源可用。然後一個進程調用se m_wait由於這個時候信號燈的值爲1,所以這個函數返回,打印機開始打印了,同時信號燈的值爲0 了。 如果第二個進程要打印,調用sem_wait時候,由於信號燈的值爲0,資源不可用,於是被阻塞了。當第一個進程打印完成以後,調用sem_post信號燈的值爲1了,這個時候系統通知第二個進程,於是第二個進程的sem_wait返回。第二個進程開始打印了。

    不過我們可以使用線程來解決這個問題的。我們會在後面解釋什麼是線程的。編譯包含上面這幾個函數的程序要加上 -lrt選賢,以連接librt.so庫2.System V信號量 爲了解決上面哪個問題,我們也可以使用System V信號量。很幸運的是Linux實現了System V信號量。這樣我們就可以用實例來解釋了。 System V信號量的函數主要有下面幾個。

    #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> key_t ftok(char *pathname,char proj);int semget(key_t key,int nsems,int semflg);int semctl(int semid,int semnum,int cmd,union semun arg);int semop(int semid,struct sembuf *spos,int nspos);struct sembuf { short sem_num; /* 使用那一個信號 */ short sem_op; /* 進行什麼操作 */ short sem_flg; /* 操作的標誌 */ };ftok函數是根據pathname和proj來創建一個關鍵字。semget創建一個信號量。成功時返回信號的ID,key是一個關鍵字,可以是用ftok創建的也可以是IPC_PRIVATE表明由系統選用一個關鍵字。 nsems表明我們創建的信號個數。semflg是創建的權限標誌,和我們創建一個文件的標誌相同。

    semctl對信號量進行一系列的控制。semid是要操作的信號標誌,semnum是信號的個數,cm d是操作的命令。經常用的兩個值是:SETVAL(設置信號量的值)和IPC_RMID(刪除信號燈)。

    arg是一個給cmd的參數。

    semop是對信號進行操作的函數。semid是信號標誌,spos是一個操作數組表明要進行什麼操作,nspos表明數組的個數。 如果sem_op大於0,那麼操作將sem_op加入到信號量的值中,並喚醒等待信號增加的進程。 如果爲0,當信號量的值是0的時候,函數返回,否則阻塞直到信號量的值爲0. 如果小於0,函數判斷信號量的值加上這個負值。如果結果爲0喚醒等待信號量爲0的進程,如果小與0函數阻塞。如果大於0,那麼從信號量裏面減去這個值並返回……

    下面我們一以一個實例來說明這幾個函數的使用方法。這個程序用標準錯誤輸出來代替我們用的打印機。

    #include <stdio.h> #include <unistd.h> #include <limits.h> #include <errno.h> #include <string.h> #include <stdlib.h> #include <sys/stat.h> #include <sys/wait.h> #include <sys/ipc.h> #include <sys/sem.h> #define PERMS S_IRUSR|S_IWUSR void init_semaphore_struct(struct sembuf *sem,int semnum,int semop,int semflg)

    { /* 初始話信號燈結構 */ sem->sem_num=semnum;sem->sem_op=semop;sem->sem_flg=semflg;} int del_semaphore(int semid)

    { /* 信號燈並不隨程序的結束而被刪除,如果我們沒刪除的話(將1改爲0)

    可以用ipcs命令查看到信號燈,用ipcrm可以刪除信號燈的*/ #if 1 return semctl(semid,0,IPC_RMID);#endif { int main(int argc,char **argv)

    { char buffer[MAX_CANON],*c;int i,n;int semid,semop_ret,status;pid_t childpid;struct sembuf semwait,semsignal;if()argc!=2(||)(n=atoi)argv[1]()<1()

    { fprintf(stderr,"Usage:%s number/n/a",argv[0]);exit(1);} /* 使用IPC_PRIVATE 表示由系統選擇一個關鍵字來創建 */ /* 創建以後信號燈的初始值爲0 */ if()semid=semget(IPC_PRIVATE,1,PERMS)(==-1)

    { fprintf(stderr,"[%d]:Acess Semaphore Error:%s/n/a",getpid)(,strerror)errno();exit(1);} /* semwait是要求資源的操作(-1) */ init_semaphore_struct(&semwait,0,-1,0);/* semsignal是釋放資源的操作(+1) */ init_semaphore_struct(&semsignal,0,1,0);/* 開始的時候有一個系統資源(一個標準錯誤輸出) */ if(semop)semid,&semsignal,1(==-1)

    { fprintf(stderr,"[%d]:Increment Semaphore Error:%s/n/a",getpid)(,strerror)errno();if(del_semaphore)semid(==-1)

    fprintf(stderr,"[%d]:Destroy Semaphore Error:%s/n/a",getpid)(,strerror)errno();exit(1);{ /* 創建一個進程鏈 */ for(i=0;i<n;i++)

    if(childpid=fork)() break;sprintf(buffer,"[i=%d]——>[Process=%d]——>[Parent=%d]——>[Child=%d]/n",i,getpid)(,getppid)(,childpid);c=buffer;/* 這裏要求資源,進入原子操作 */ while()(semop_ret=semop)semid,&semwait,1()==-1(&&amp;)errno==EINTR();if(semop_ret==-1)

    { fprintf(stderr,"[%d]:Decrement Semaphore Error:%s/n/a",getpid)(,strerror)errno();} else { while(*c!='')fputc(*c++,stderr);/* 原子操作完成,趕快釋放資源 */ while()(semop_ret=semop)semid,&semsignal,1()==-1(&&amp;)errno==EINTR();if(semop_ret==-1)

    fprintf(stderr,"[%d]:Increment Semaphore Error:%s/n/a",getpid)(,strerror)errno();{ /* 不能夠在其他進程反問信號燈的時候,我們刪除了信號燈 */ while()wait(&status)==-1(&&)errno==EINTR();/* 信號燈只能夠被刪除一次的 */ if(i==1)

    if(del_semaphore)semid(==-1)

    fprintf(stderr,"[%d]:Destroy Semaphore Error:%s/n/a",getpid)(,strerror)errno();exit(0);{信號燈的主要用途是保護臨界資源(在一個時刻只被一個進程所擁有)。

    3.SystemV消息隊列 爲了便於進程之間通信,我們可以使用管道通信 SystemV也提供了一些函數來實現進程的通信。這就是消息隊列。

    #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgget(key_t key,int msgflg);int msgsnd(int msgid,struct msgbuf *msgp,int msgsz,int msgflg);int msgrcv(int msgid,struct msgbuf *msgp,int msgsz,long msgtype,int msgflg);int msgctl(Int msgid,int cmd,struct msqid_ds *buf);

    struct msgbuf { long msgtype; /* 消息類型 */…… /* 其他數據類型 */ } msgget函數和semget一樣,返回一個消息隊列的標誌。msgctl和semctl是對消息進行控制…… msgsnd和msgrcv函數是用來進行消息通訊的。msgid是接受或者發送的消息隊列標誌。

    msgp是接受或者發送的內容。msgsz是消息的大小。 結構msgbuf包含的內容是至少有一個爲msgtype.其他的成分是用戶定義的。對於發送函數msgflg指出緩衝區用完時候的操作。

    接受函數指出無消息時候的處理。一般爲0. 接收函數msgtype指出接收消息時候的操作。

    如果msgtype=0,接收消息隊列的第一個消息。大於0接收隊列中消息類型等於這個值的第一個消息。小於0接收消息隊列中小於或者等於msgtype絕對值的所有消息中的最小一個消息。 我們以一個實例來解釋進程通信。下面這個程序有server和client組成。先運行服務端後運行客戶端。

    服務端 server.c #include <stdio.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/stat.h> #include <sys/msg.h> #define MSG_FILE "server.c" #define BUFFER 255 #define PERM S_IRUSR|S_IWUSR struct msgtype { long mtype;char buffer[BUFFER+1];};int main()

    { struct msgtype msg;key_t key;int msgid;if()key=ftok(MSG_FILE,'a')(==-1)

    { fprintf(stderr,"Creat Key Error:%s/a/n",strerror)errno();exit(1);} if()msgid=msgget(key,PERM|IPC_CREAT|IPC_EXCL)(==-1)

    { fprintf(stderr,"Creat Message Error:%s/a/n",strerror)errno();exit(1);} while(1)

    { msgrcv(msgid,&msg,sizeof)struct msgtype(,1,0);fprintf(stderr,"Server Receive:%s/n",msg.buffer);msg.mtype=2;msgsnd(msgid,&msg,sizeof)struct msgtype(,0);} exit(0);{

----------------------------------------------------------------------------
----

    客戶端(client.c)

    #include <stdio.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <sys/stat.h> #define MSG_FILE "server.c" #define BUFFER 255 #define PERM S_IRUSR|S_IWUSR struct msgtype { long mtype;char buffer[BUFFER+1];};int main(int argc,char **argv)

    { struct msgtype msg;key_t key;int msgid;if(argc!=2)

    { fprintf(stderr,"Usage:%s string/n/a",argv[0]);exit(1);} if()key=ftok(MSG_FILE,'a')(==-1)

    { fprintf(stderr,"Creat Key Error:%s/a/n",strerror)errno();exit(1);} if()msgid=msgget(key,PERM)(==-1)

    { fprintf(stderr,"Creat Message Error:%s/a/n",strerror)errno();exit(1);} msg.mtype=1;strncpy(msg.buffer,argv[1],BUFFER);msgsnd(msgid,&msg,sizeof)struct msgtype(,0);memset(&msg,'',sizeof)struct msgtype();msgrcv(msgid,&msg,sizeof)struct msgtype(,2,0);fprintf(stderr,"Client receive:%s/n",msg.buffer);exit(0);{注意服務端創建的消息隊列最後沒有刪除,我們要使用ipcrm命令來刪除的。

    4.SystemV共享內存 還有一個進程通信的方法是使用共享內存。SystemV提供了以下幾個函數以實現共享內存。

    #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key,int size,int shmflg);void *shmat(int shmid,const void *shmaddr,int shmflg);int shmdt(const void *shmaddr);int shmctl(int shmid,int cmd,struct shmid_ds *buf);shmget和shmctl沒有什麼好解釋的。size是共享內存的大小。 shmat是用來連接共享內存的。shmdt是用來斷開共享內存的。不要被共享內存詞語嚇倒,共享內存其實很容易實現和使用的。shmaddr,shmflg我們只要用0代替就可以了。在使用一個共享內存之前我們調用s hmat得到共享內存的開始地址,使用結束以後我們使用shmdt斷開這個內存。

    #include <stdio.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #define PERM S_IRUSR|S_IWUSR int main(int argc,char **argv)

    { int shmid;char *p_addr,*c_addr;if(argc!=2)

    { fprintf(stderr,"Usage:%s/n/a",argv[0]);exit(1);} if()shmid=shmget(IPC_PRIVATE,1024,PERM)(==-1)

    { fprintf(stderr,"Create Share Memory Error:%s/n/a",strerror)errno();exit(1);} if(fork)()

    { p_addr=shmat(shmid,0,0);memset(p_addr,'',1024);strncpy(p_addr,argv[1],1024);exit(0);} else { c_addr=shmat(shmid,0,0);printf("Client get %s",c_addr);exit(0);}這個程序是父進程將參數寫入到共享內存,然後子進程把內容讀出來。最後我們要使用ip crm釋放資源的。先用ipcs找出ID然後用ipcrm shm ID刪除。

    後記:進程通信(IPC)是網絡程序的基礎,在很多的網絡程序當中會大量的使用進程通信的概念和知識。其實進程通信是一件非常複雜的事情,我在這裏只是簡單的介紹了一下。如果你想學習進程通信的詳細知識,最好的辦法是自己不斷的寫程序和看聯機手冊。現在網絡上有了很多的知識可以去參考。可惜我看到的很多都是英文編寫的。如果你找到了有中文的版本請儘快告訴我。謝謝!

    7(Linux程序設計入門——線程操作前言:Linux下線程的創建介紹在Linux下線程的創建和基本的使用。 Linux下的線程是一個非常複雜的問題,由於我對線程的學習不時很好,我在這裏只是簡單的介紹線程的創建和基本的使用,關於線程的高級使用)如線程的屬性,線程的互斥,線程的同步等等問題(可以參考我後面給出的資料。 現在關於線程的資料在網絡上可以找到許多英文資料,後面我羅列了許多鏈接,對線程的高級屬性感興趣的話可以參考一下。 等到我對線程的瞭解比較深刻的時候,我回來完成這篇文章。如果您對線程瞭解的詳盡我也非常高興能夠由您來完善。

    先介紹什麼是線程。我們編寫的程序大多數可以看成是單線程的。就是程序是按照一定的順序來執行。如果我們使用線程的話,程序就會在我們創建線成的地方分叉,變成兩個"程序"在執行。粗略的看來好象和子進程差不多的,其實不然。子進程是通過拷貝父進程的地址空間來執行的。而線程是通過共享程序代碼來執行的,講的通俗一點就是線程的相同的代碼會被執行幾次。使用線程的好處是可以節省資源,由於線程是通過共享代碼的,所以沒有進程調度那麼複雜。

    線程的創建和使用線程的創建是用下面的幾個函數來實現的。

    #include <pthread.h> int pthread_create(pthread_t *thread,pthread_attr_t *attr,void *)*start_routine()void *(,void *arg);void pthread_exit(void *retval);int pthread_join(pthread *thread,void **thread_return);pthread_create創建一個線程,thread是用來表明創建線程的ID,attr指出線程創建時候的屬性,我們用NULL來表明使用缺省屬性。start_routine函數指針是線程創建成功後開始執行的函數,arg是這個函數的唯一一個參數。表明傳遞給start_routine的參數。 pthrea d_exit函數和exit函數類似用來退出線程。這個函數結束線程,釋放函數的資源,並在最後阻塞,直到其他線程使用pthread_join函數等待它。然後將*retval的值傳遞給**thread_ return.由於這個函數釋放所以的函數資源,所以retval不能夠指向函數的局部變量。 pt hread_join和wait調用一樣用來等待指定的線程。 下面我們使用一個實例來解釋一下使用方法。在實踐中,我們經常要備份一些文件。下面這個程序可以實現當前目錄下的所有文件備份。備份後的後綴名爲bak #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <pthread.h> #include <dirent.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/time.h> #define BUFFER 512 struct copy_file { int infile;int outfile;};void *copy(void *arg)

    { int infile,outfile;int bytes_read,bytes_write,*bytes_copy_p;char buffer[BUFFER],*buffer_p;struct copy_file *file=(struct copy_file *)arg;infile=file->infile;outfile=file->outfile;/* 因爲線程退出時,所有的變量空間都要被釋放,所以我們只好自己分配內存了 */ if()bytes_copy_p=(int *)malloc(sizeof)int()(==NULL) pthread_exit(NULL);bytes_read=bytes_write=0;*bytes_copy_p=0;/* 還記得怎麼拷貝文件嗎 */ while()bytes_read=read(infile,buffer,BUFFER)(!=0)

    { if()bytes_read==-1(&&)errno!=EINTR()break;else if(bytes_read>0)

    { buffer_p=buffer;while()bytes_write=write(outfile,buffer_p,bytes_read)(!=0)

    { if()bytes_write==-1(&&)errno!=EINTR()break;else if(bytes_write==bytes_read)break;else if(bytes_write>0)

    { buffer_p+=bytes_write;bytes_read-=bytes_write;} if(bytes_write==-1)break;*bytes_copy_p+=bytes_read;{ close(infile);close(outfile);pthread_exit(bytes_copy_p);} int main(int argc,char **argv)

    { pthread_t *thread;struct copy_file *file;int byte_copy,*byte_copy_p,num,i,j;char filename[BUFFER];struct dirent **namelist;struct stat filestat;/* 得到當前路徑下面所有的文件(包含目錄)的個數 */ if()num=scandir(".",&namelist,0,alphasort)(<0)

    { fprintf(stderr,"Get File Num Error:%s/n/a",strerror)errno();exit(1)

    發表於 @ 2006年03月30日 12:22 AM | 評論 (0)

    UNIX/Linux編程相關工具和資源

    1.linux編程所用的一些工具GCC 中文手冊http://www.nbfan.com/forum/dispbbs……&ID=1433&page=1 GNU make 指南http://www.linuxsir.org/bbs/showthr……&threadid=40431 autoconf-2.57手冊(英文)

    http://www.gnu.org/software/autocon……toconf_toc.html Autoconf-2.13手冊http://www.linuxforum.net/books/autoconf.html使用 automake http://263.aka.org.cn/Lectures/002/……-2.1.4/230.html使用CVS進行版本管理http://www.linuxforum.net/books/cvsintro.html CVS用後感http://www.linuxsir.org/bbs/showthread.php?t=170538

    linux下常用調試工具:非常好的gdb教程,強烈推薦:http://www.linuxsir.org/bbs/showthread.php?t=171156 GDB英文文檔http://oldsite.linuxaid.com.cn/deve……howdev.jsp?i=49 gdb基本用法http://www.linuxsir.com/bbs/showthr……&threadid=45731 gdb的官方文檔http://www.gnu.org/software/gdb/documentation/

    linux編程基礎:要入門先看這個,Linux下C語言編程基礎知識http://www.linuxsir.com/bbs/showthr……=&threadid=1005 Linux 下 C 語言編程http://www.linuxsir.org/bbs/showthr……=&threadid=7192 Linux下的C++編程http://www.linuxsir.org/bbs/showthr……6723#post276723 linux下的應用程序開發http://www.lisoleg.net/lisoleg/applications/index.html

    參考書籍Linux程序員指南(The Linux Programmer's Guide)

    http://www.linuxhq.com/guides/LPG/lpg.html UNIX編程環境(The UNIX Programming Environment)

    http://www.iu.hio.no/~mark/unix/unix_toc.html UNIX編程的藝術(The Art of Unix Programming)

    http://www.catb.org/~esr/writings/taoup/html/

    進程與線程Linux下的多進程編程初步http://www.china-pub.com/computers/emook/0439/info.htm多進程編程http://www.linuxsir.org/bbs/showthr……&threadid=44083 Linux下的多進程編程http://www.linuxsir.com/bbs/showthr……&threadid=48887進程的創建http://www.linuxsir.org/bbs/showthr……&threadid=44078 POSIX Threads Programming http://www.llnl.gov/computing/tutor……reads/MAIN.html

    linux系統調用系統調用列表,編程必備http://www-900.ibm.com/developerWor……/appendix.shtml C語言庫函數——unix常用系統調用——使用說明http://www.linuxsir.org/bbs/showthr……threadid=119136 http://chinaunix.net/forum/viewtopic.php?t=72159

    FAQ列表UNIX Programming FAQ 中文版,很有參考價值http://www.linuxforum.net/books/upfaq/book1.htm UNIX Socket FAQ http://www.developerweb.net/sock-faq/

    編碼規範Linux內核編程風格http://www.linuxsir.org/bbs/showthr……=&postid=257594 GNU編碼標準http://www.linuxforum.net/forum/sho……op&Number=29053

    可執行文件格式與庫文件從程序員角度看ELF http://www.linuxsir.com/bbs/showthr……&threadid=48381 ELF可執行聯接規範(英漢對照版)

    http://www.linuxaid.com.cn/articles……014528121.shtml Linux動態鏈接庫高級應用http://www.linuxsir.com/bbs/showthr……&threadid=18889鏈接器與加載器,非常重要的一本書(Linkers and Loaders )

    http://www.iecc.com/linker/

    終端與串口編程、設備文件UNIX下c語言的圖形編程——curses.h 函式庫http://www.fanqiang.com/a4/b2/20020626/060200258.html VT100控制碼http://www.linuxsir.com/bbs/showthr……&threadid=43530 Linux ioctl() Primer http://www.linuxsir.org/bbs/showthr……&threadid=44268 Linux Serial Programming How-to English version: http://www.sgmltools.org/HOWTO/Seri……g-HOWTO/t1.html Linux Serial Programming How-to 中文繁體版:http://www.linux.org.tw/CLDP/OLD/Se……ming-HOWTO.html

    linux中文化:

    UTF-8 and Unicode FAQ http://www.linuxforum.net/books/UTF-8-Unicode.html unicode 如何轉換爲ASCII?

    http://www.linuxforum.net/forum/gsh……=5&o=all&fpart=一個臺灣的linux中文化站點http://xcin.linux.org.tw/i18n/ UTF-8 and Unicode FAQ for Unix/Linux http://www.cl.cam.ac.uk/~mgk25/unicode.html Linux Unicode 編程http://www-900.ibm.com/developerWor……uni/index.shtml Linux 國際化本地化和中文化http://www.linuxforum.net/doc/i18n-new.html

    一些很好的編程資源連接:

    HPCVL編程資源連接http://www.hpcvl.org/faqs/prog_links.html Linux Programming Resources http://leapster.org/linoleum/

    一個綜合的編程資源站點http://www.clipx.net/norton.php

    發表於 @ 2006年03月30日 12:17 AM | 評論 (0)

    Linux內核編碼風格

    Linux內核編碼風格

    這篇簡短的文章描述了Linux內核首選的編碼風格。編碼風格是很個人化的東西,我不會把自己的觀點強加給任何人。但是,Linux內核的代碼畢竟是我必須有能力維護的,因此我寧願它的編碼風格是我喜歡的。請至少考慮一下這一點。

    首先,我建議打印一份《GNU編碼標準》,不要閱讀它。燒掉它,它不過是象徵性的姿態。

    然後,請看:

    第 1 章: 縮進

    Tabs(製表符)是8個字符的大小,因此縮進也應該是8個字符的大小。有些叛逆主張試圖把縮進變成4個(甚至是2個!)字符的長度,這就好象試圖把PI(案,圓周率)定義成3是一樣的。

    依據:縮進背後的思想是:清楚地定義一個控制塊從哪裏開始,到哪裏結束。尤其是在你連續不斷的盯了20個小時的屏幕後,如果你有大尺寸的縮進。你將更容易發現縮進的好處。

    現在,有些人說8個字符大小的縮進導致代碼太偏右了,並且在一個80字符寬的終端屏幕上看着很不舒服。對這個問題的回答是:如果你有超過3個級別的縮進,你就有點犯糊塗了,應當修改你的程序。

    簡而言之,8個字符的縮進使程序更易讀,而且當你把功能隱藏的太深時,多層次的縮進還會對此很直觀的給出警告。要留心這種警告信息。

    第 2 章: 放置花括號

    C程序中另一個要主意的就是花括號的放置。與縮進尺寸不同的是,關於如何放置花括號沒有技術上的理由。但是,首選的方法是象先知Brain Kernighan和Dennis Ritchie展現的那樣:把左括號放在行尾,右括號放在行首。也就是:

    if (x is true) { we do y }

    然而,還有另外一種情況,就是函數:函數應當把左右括號都放在行首。也就是:

    int function(int x)

    { body of function }

    叛逆的人們所在皆有。他們說,這樣會導致…嗯,不一致性(案,指函數的花括號使用與其他情況不統一)。但是所有正確思考的人都知道:(1) K&R是正確的;(2) K&R還是正確的。 而且,函數與別任何東西都不一樣(在C語言中你沒法隱藏它)。

    注意,右括號所在的行不應當有其它東西,除非跟隨着一個條件判斷。也就是do-while語句中的“while”和if-else語句中的“else”。象這樣:

    do { body of do-loop } while (condition);

    和:

    if (x == y) {……

    { else if (x > y) }……

    { else }……

    {

    依據: K&R.而且,注意這種花括號的放置減少了空行的數目,並沒損害可讀性。因此,當屏幕上不可以有很多空行時(試想25行的終端屏幕),你就有更多的空行來安插註釋。

    第 3 章: 命名

    C是一門樸素的語言,你使用的命名也應該這樣。與Modula-2和Pascal程序員不同,C程序員不使用諸如“ThisVariableIsATemporaryCounter”這樣“聰明”的名字。C程序員應該叫它“tmp”,這寫起來更簡單,也不會更難懂。

    然而,當面對複雜情況時就有些棘手,給全局變量取一個描述性的名字是必要的。把一個全局函數叫做“foo”是一種目光短淺的行爲。

    全局變量(只當你確實需要時才用)應該有描述性的名字,全局函數也一樣。如果你有一個統計當前用戶個數的函數,應當把它命名爲“count_active_user()”或者簡單點些的類似名稱,不應該命名爲“cntusr()”。

    把函數類型寫進函數名(即所謂的“匈牙利命名法”)簡直就是大腦有問題──編譯器總是知道函數的類型並且能加以檢查,這種命名法只會弄糊塗程序員自己。怪不得微軟總是製造充滿bug的程序。

    局部變量的名字應該儘量短,而且說到點子上。如果你有個普通的整型循環計數變量,應當命名爲“i”。命名爲“loop_counter”並不能帶來任何成效,如果它不被誤解的話(案,這裏的言外之意是說,如果被誤解就更慘了)。與此類似,“tmp”可以作爲一個用來存儲任何類型臨時值的變量的名字。

    如果你害怕弄混淆局部變量(s)的名字,你就面臨着另一個問題,也叫作“函數增長荷爾蒙失調綜合症”。請參考下一章。

    第 4 章: 函數

    函數應當短而精美,而且只做一件事。它們應當佔滿1或2個屏幕(就象我們知道的那樣,ISO/ANSI的屏幕大小是80X24),只做一件事並且把它做好。

    一個函數的最大長度與它的複雜度和縮進級別成反比。所以,如果如果你有一個概念上簡單(案,“簡單”是simple而不是easy)的函數,它恰恰包含着一個很長的case語句,這樣你不得不爲不同的情況準備不懂的處理,那麼這樣的長函數是沒問題的。

    然而,如果你有一個複雜的函數,你猜想一個並非天才的高一學生可能看不懂得這個函數,你就應當努力把它減縮得更接近前面提到的最大函數長度限制。可以使用一些輔助函數,給它們取描述性的名字(如果你認爲這些輔助函數的調用是性能關鍵的,可以讓編譯器把它們內聯進來,這比在單個函數內完成所有的事情通常要好些)。

    對函數還存在另一個測量標準:局部變量的數目。這不該超過5到10個,否則你可能會弄錯。應當重新考慮這個函數,把它分解成小片。人類的大腦一般能同時記住7個不同的東西,超過這個數目就會犯糊塗。或許你認爲自己很聰明,那麼請你理解一下從現在開始的2周時間你都做什麼了。

    第 5 章:註釋

    註釋是有用的,但過量的註釋則是有害的。不要試圖在註釋中解釋你的代碼是如何工作的:把代碼是如何工作的視爲一件顯然的事情會更好些,而且,給糟糕的代碼作註釋則是在浪費時間。

    通常,你願意自己的註釋說出代碼是做什麼的,而不是如何做。還有,儘量避免在函數體內作註釋:如果函數很複雜,你很可能需要分開來註釋,回頭到第4章去看看吧。你可以給一段代碼──漂亮的或醜陋的──作註釋以引起注意或警告,但是不要過量。取而代之,應當把註釋放在函數首部,告訴人們該函數作什麼,而不是爲什麼這樣做。

    第 6 章:你把事情弄亂了

    好吧,我們來看看。很可能有長期使用UNIX的人告訴過你,“GNU emacs”能自動爲你格式化C程序源代碼,你注意到這是真的,它確實能做到,但是缺省情況下它的用處遠遠小於期望值──鍵入無數的monkeys到GNU emacs中絕不可能造出好的程序。

    因此,你可以或者刪除GNU emacs,或者對它進行理智的配置。對於後者,可以把下面的行粘貼到你的。emacs文件中:

    (defun linux-c-mode )(

    "C mode with adjusted defaults for use with the Linux kernel."(interactive)

    (c-mode)

    (c-set-style "K&R")

    (setq c-basic-offset 8)(

    這將會定義一個把C代碼弄成linux風格的命令。當hacking一個模塊時,如果你把“-*- linux-c -*-”放到了最初的兩行,這個模塊將被自動調用。而且,如果你打算每當在/usr/src/linux下編輯源文件時就自動調用它,也許你會把下面的命令:(setq auto-mode-alist )cons '("/usr/src/linux.*/.*//.[ch]$" . linux-c-mode)

    auto-mode-alist()

    添加進你的。emacs文件。

    但是,即使你沒能讓emacs正確做到格式化,也並非將就此一無所有:還有“indent”程序呢。

    嗯,再提醒一下,GNU indent跟GNU emacs有同樣的毛病,這就需要你給它一些命令行選項。然而,這不是很糟糕的事,因爲即使是GNU indent也承認K&R的權威性(GNU的人不是魔鬼,他們只是在這裏太過嚴格了,以致於誤導人),所以你可以只需給indent這樣的選項:“-kr -i8”(表示“K&R風格,8個字符的縮進”)。

    “indent”程序有很多選項,特別是當爲重排過的程序作註釋的時候,你需要看一下它的手冊。記住:“indent”可不是修正糟糕程序的萬能鑰匙。

    第 7 章: 配置文件(configuration-files)

    對配置選項來說(arch/xxx/config.in和所有的Config.in文件),使用不同的縮進風格。

    若代碼中的縮進級別爲3,配置選項就應該爲2,這樣可以暗示出依賴關係。後者只是用於bool/tristate(即二態/三態)的選項。對其它情況用常識就行了。舉例來說:

    if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then tristate 'Apply nitroglycerine inside the keyboard (DANGEROUS)' CONFIG_BOOM if [ "$CONFIG_BOOM" != "n" ]; then bool '  Output nice messages when you explode' CONFIG_CHEER fi

    通常CONFIG_EXPERIMENTAL應當在所有不穩定的選項的周圍出現。所有已知會破壞數據的選項(如文件系統的實驗性的寫支持功能)應當被標記爲(DANGEROUS),其他實驗性的選項應當被標記爲(EXPERIMENTAL)。

    第 8 章: 數據結構

    假如數據結構在其被創建/銷燬的線程環境(案:這裏說的線程是一個執行實體,可能是進程、內核線程或其它)之外還具有可見性,那麼他們都該有引用計數。 在內核中沒有垃圾收集機制(而且內核之外的垃圾收集也是緩慢而低效的),這意味着你絕對應該爲每一次使用進行引用計數。

    引用計數意味着你可以避開鎖,還能允許多個線程並行訪問該數據結構──而且不用擔心僅僅因爲訪問數據結構的線程睡眠了一會兒或者幹別的去了,它們就會消失。

    注意,鎖不是引用計數的替代品。鎖是用來保持數據結構的一致性的,而引用計數是一種內存管理技術。通常二者都需要,而且不會彼此混淆。

    確實有許多數據結構可以有兩個級別的引用計數,當使用者具有不同的“等級”(classes)時就是這樣。子等級(subclass)記錄了處於該等級的使用者個數,而且當它減到零的時候就把總體計數(global count)減一。

    這種“多級引用計數”(multi-reference-counting)的一個實例可以在內存管理子系統("struct mm_struct":mm_users和mm_count)中找到,也可以在文件系統的代碼中("struct super_block":s_count和s_active)找到。

    記住:如果另一個線程能找到你的數據結構,而你有沒對它做引用計數,那幾乎可以肯定:這是一個bug.

    發表於 @ 2006年03月30日 12:16 AM | 評論 (0)

    理解複雜的C/C++聲明

    曾經碰到過讓你迷惑不解、類似於int * (* )*fp1( )int( ) [10];這樣的變量聲明嗎?本文將由易到難,一步一步教會你如何理解這種複雜的C/C++聲明。

    我們將從每天都能碰到的較簡單的聲明入手,然後逐步加入const修飾符和typedef,還有函數指針,最後介紹一個能夠讓你準確地理解任何C/C++聲明的“右左法則”。

    需要強調一下的是,複雜的C/C++聲明並不是好的編程風格;我這裏僅僅是教你如何去理解這些聲明。注意:爲了保證能夠在同一行上顯示代碼和相關注釋,本文最好在至少1024x768分辨率的顯示器上閱讀。

    基礎

    讓我們從一個非常簡單的例子開始,如下:

    int n;

    這個應該被理解爲“declare n as an int”(n是一個int型的變量)。

    接下去來看一下指針變量,如下:

    int *p;

    這個應該被理解爲“declare p as an int *”(p是一個int *型的變量),或者說p是一個指向一個int型變量的指針。我想在這裏展開討論一下:我覺得在聲明一個指針(或引用)類型的變量時,最好將*(或&)寫在緊靠變量之前,而不是緊跟基本類型之後。這樣可以避免一些理解上的誤區,比如:

    int*   p,q;

    第一眼看去,好像是p和q都是int*類型的,但事實上,只有p是一個指針,而q是一個最簡單的int型變量。

    我們還是繼續我們前面的話題,再來看一個指針的指針的例子:

    char **argv;

    理論上,對於指針的級數沒有限制,你可以定義一個浮點類型變量的指針的指針的指針的指針……

    再來看如下的聲明:

    int RollNum[30][4];int (*p)[4]=RollNum;int *q[5];

    這裏,p被聲明爲一個指向一個4元素(int類型)數組的指針,而q被聲明爲一個包含5個元素(int類型的指針)的數組。

    另外,我們還可以在同一個聲明中混合實用*和&,如下:

    int **p1;  //  p1 is a pointer  ;  to a pointer   to an int. int *&p2;  //  p2 is a reference to a pointer   to an int. int &*p3;  //  ERROR: Pointer    to a reference is illegal. int &&p4;  //  ERROR: Reference  to a reference is illegal.

    注:p1是一個int類型的指針的指針;p2是一個int類型的指針的引用;p3是一個int類型引用的指針(不合法!);p4是一個int類型引用的引用(不合法!)。

    const修飾符

    當你想阻止一個變量被改變,可能會用到const關鍵字。在你給一個變量加上const修飾符的同時,通常需要對它進行初始化,因爲以後的任何時候你將沒有機會再去改變它。例如:

    const int n=5;int const m=10;

    上述兩個變量n和m其實是同一種類型的——都是const int(整形恆量)。因爲C++標準規定,const關鍵字放在類型或變量名之前等價的。我個人更喜歡第一種聲明方式,因爲它更突出了const修飾符的作用。

    當const與指針一起使用時,容易讓人感到迷惑。例如,我們來看一下下面的p和q的聲明:

    const int *p;int const *q;

    他們當中哪一個代表const int類型的指針(const直接修飾int),哪一個代表int類型的const指針(const直接修飾指針)?實際上,p和q都被聲明爲const int類型的指針。而int類型的const指針應該這樣聲明:

    int * const r= &n; // n has been declared as an int

    這裏,p和q都是指向const int類型的指針,也就是說,你在以後的程序裏不能改變*p的值。而r是一個const指針,它在聲明的時候被初始化指向變量n(即r=&n;)之後,r的值將不再允許被改變(但*r的值可以改變)。

    組合上述兩種const修飾的情況,我們來聲明一個指向const int類型的const指針,如下:

    const int * const p=&n // n has been declared as const int

    下面給出的一些關於const的聲明,將幫助你徹底理清const的用法。不過請注意,下面的一些聲明是不能被編譯通過的,因爲他們需要在聲明的同時進行初始化。爲了簡潔起見,我忽略了初始化部分;因爲加入初始化代碼的話,下面每個聲明都將增加兩行代碼。

    char ** p1;                    //        pointer to       pointer to       char const char **p2;               //        pointer to       pointer to const char char * const * p3;             //        pointer to const pointer to       char const char * const * p4;       //        pointer to const pointer to const char char ** const p5;              //  const pointer to       pointer to       char const char ** const p6;        //  const pointer to       pointer to const char char * const * const p7;       //  const pointer to const pointer to       char const char * const * const p8; //  const pointer to const pointer to const char

    注:p1是指向char類型的指針的指針;p2是指向const char類型的指針的指針;p3是指向char類型的const指針;p4是指向const char類型的const指

    針;p5是指向char類型的指針的const指針;p6是指向const char類型的指針的const指針;p7是指向char類型const指針的const指針;p8是指向const char類型的const指針的const指針。

    typedef的妙用

    typedef給你一種方式來克服“*只適合於變量而不適合於類型”的弊端。你可以如下使用typedef:

    typedef char * PCHAR;PCHAR p,q;

    這裏的p和q都被聲明爲指針。(如果不使用typedef,q將被聲明爲一個char變量,這跟我們的第一眼感覺不太一致!)下面有一些使用typedef的聲明,並且給出瞭解釋:

    typedef char * a;  // a is a pointer to a char

    typedef a b();     // b is a function that returns // a pointer to a char

    typedef b *c;      // c is a pointer to a function // that returns a pointer to a char

    typedef c d();     // d is a function returning // a pointer to a function // that returns a pointer to a char

    typedef d *e;      // e is a pointer to a function // returning  a pointer to a // function that returns a // pointer to a char

    e var[10];         // var is an array of 10 pointers to // functions returning pointers to // functions returning pointers to chars.

    typedef經常用在一個結構聲明之前,如下。這樣,當創建結構變量的時候,允許你不使用關鍵字struct(在C中,創建結構變量時要求使用struct關鍵字,如struct tagPOINT a;而在C++中,struct可以忽略,如tagPOINT b)。

    typedef struct tagPOINT { int x;int y;}POINT;

    POINT p; /* Valid C code */

    函數指針

    函數指針可能是最容易引起理解上的困惑的聲明。函數指針在DOS時代寫TSR程序時用得最多;在Win32和X-Windows時代,他們被用在需要回調

    函數的場合。當然,還有其它很多地方需要用到函數指針:虛函數表,STL中的一些模板,Win NT/2K/XP系統服務等。讓我們來看一個函數指針的簡單例子:

    int (*p)(char);

    這裏p被聲明爲一個函數指針,這個函數帶一個char類型的參數,並且有一個int類型的返回值。另外,帶有兩個float類型參數、返回值是char類型的指針的指針的函數指針可以聲明如下:

    char ** (*p)(float, float);

    那麼,帶兩個char類型的const指針參數、無返回值的函數指針又該如何聲明呢?參考如下:

    void * (*a[5])(char * const, char * const);

    “右左法則”[重要!!!]

    The right-left rule: Start reading the declaration from the innermost parentheses, go right, and then go left. When you encounter parentheses, the direction should be reversed. Once everything in the parentheses has been parsed, jump out of it. Continue till the whole declaration has been parsed.

    這是一個簡單的法則,但能讓你準確理解所有的聲明。這個法則運用如下:從最內部的括號開始閱讀聲明,向右看,然後向左看。當你碰到一個括號時就調轉閱讀的方向。括號內的所有內容都分析完畢就跳出括號的範圍。這樣繼續,直到整個聲明都被分析完畢。

    對上述“右左法則”做一個小小的修正:當你第一次開始閱讀聲明的時候,你必須從變量名開始,而不是從最內部的括號。

    下面結合例子來演示一下“右左法則”的使用。

    int * (* )*fp1( )int( ) [10];

    閱讀步驟:1. 從變量名開始 —— fp1 2. 往右看,什麼也沒有,碰到了(,因此往左看,碰到一個* —— 一個指針3. 跳出括號,碰到了)int( —— 一個帶一個int參數的函數4. 向左看,發現一個* —— )函數(返回一個指針5. 跳出括號,向右看,碰到[10] —— 一個10元素的數組6. 向左看,發現一個* —— 指針7. 向左看,發現int —— int類型

    總結:fp1被聲明成爲一個函數的指針的指針的數組,這個數組有10個元素,函數的原型爲帶一個int類型的參數,返回值爲一個指針?

    再來看一個例子:

    int *( *) *arr[5]()()();

    閱讀步驟:1. 從變量名開始 ——

------------------------------------ arr

    2. 往右看,發現是一個數組 —— 一個5元素的數組3. 向左看,發現一個* —— 指針4. 跳出括號,向右看,發現() —— 不帶參數的函數5. 向左看,碰到* —— (函數)返回一個指針6. 跳出括號,向右發現() —— 不帶參數的函數7. 向左,發現* —— (函數)返回一個指針8. 繼續向左,發現int —— int類型

    總結:

    還有更多的例子:

    float ( * ) *b()( [] )();              // b is a function that returns a // pointer to an array of pointers // to functions returning floats.

    void * ( *c) ( char, int )*()();       // c is a pointer to a function that takes // two parameters://     a char and a pointer to a //     function that takes no //     parameters and returns //     an int // and returns a pointer to void.

    void ** (*d) (int &,char **)*()char *, char **();        // d is a pointer to a function that takes // two parameters://     a reference to an int and a pointer //     to a function that takes two parameters://        a pointer to a char and a pointer //        to a pointer to a char //     and returns a pointer to a pointer //     to a char // and returns a pointer to a pointer to void

    float ( * ) * e[10](

    (int &) ( [5];                    // e is an array of 10 pointers to // functions that take a single // reference to an int as an argument // and return pointers to // an array of 5 floats.

    發表於 @ 2006年03月30日 12:01 AM | 評論 (0)

    看一個標準的庫函數是怎麼寫的

    char * strcpy( char *strDest, const char *strSrc )

    { assert( )strDest != NULL( && )strSrc != NULL( );char *address = strDest;while( )*strDest++ = * strSrc++( != ‘/0’ )

    ;return address;{注意的地方:1,const char *strSrc  因爲strSrc是輸入參數,不能被修改。

    2,assert的使用。頭文件加上include<assert.h>.對源地址和目的地址加非0斷言,加強程序的健壯性。如果strDest 或者strSrc爲空指針,那麼系統會提示:Assertion failed,並指出出錯位置。

    3,用了一個變量address.是爲了實現鏈式操作,將目的地址返回。比如strcpy( s,strcpy)t,a( );把a的內容複製給t,再把t的內容複製給s. 4,while( )*strDest++ = * strSrc++( != ‘/0’ )。根據優先級,先把strSrc所指內存的值賦值到strDest所指的內存中,然後strSrc指向下一塊內存,strDest也一樣。直到遇到'/0'爲止。此時,*strDest和*strSrc的值都爲'/0'.

    我自己寫了一個程序,用來驗證這個函數。但卻犯了一個錯誤。後來看了一下自己以前的筆記才發現錯誤的原因。貼出來,看你能看到錯誤在哪裏嗎?

    #include<stdio.h> #include<assert.h>

    char *strcopy(char *strDest, const char *strSrc);

    main()

    { char *s = "moonbingbing";char *t = "wenming";

    strcopy(s,t);printf("%s/n%s/n",s,t);return 0;{

    char *strcopy(char *strDest, const char *strSrc)

    { assert( )strDest != NULL( && )strSrc != NULL( );char *address = strDest;while( )*strDest++ = * strSrc++( != '/0' )

    ;return address;{

    錯就錯在char *s = "moonbingbing";char *t = "wenming";因爲在ANSI C中,指針p 指向的字符串是當作常量來處理的。如果你的編譯器是tc2.0 ,tc++3.0 ,wintc,那麼編譯器不會報錯。從這個程序可以看出,寫一個程序不難,但寫好就容易了。這些庫函數都是高手們寫的,要考慮健壯性,安全性,擴展性。這就是高手和和一般人的區別了。

    這些東西在林銳的書上見過,有一段時間沒溫習c語言了。看一遍就有一些新的收穫,溫故而知新啊。所以記下來,別再忘了。

    發表於 @ 2006年03月29日 11:53 PM | 評論 (0)

    探討scanf函數

    scanf函數我曾經在這個函數上犯過不少錯誤,也看到別人犯過的錯誤,記下來,提醒自己不要重蹈覆轍了。如果對你有用,那就更好了:(如果你發現文章中有錯誤,歡迎你不吝賜教。希望和大家一起學習。

    有關詳細的scanf函數解釋,大家可以去看看《C程序設計語言》(K&C)和《C語言大全》(後面我把其中scanf的部分貼了出來)。

    曾經錯的幾個地方:(xpsp2,vc6.0環境下)

    1.空白符問題#include<stdio.h> main()

    { int a;printf("input the data/n");scanf("%d/n",&a);//這裏多了一個回車符/n printf("%d",a);return 0;}結果要輸入兩個數程序才結束,而不是預期的一個。why?

    原因:用空白符結尾時,scanf會跳過空白符去讀下一個字符,所以你必須再輸入一個數。這裏的空白符包括空格,製表符,換行符,回車符和換頁符。所以如果你用scanf("%d  ",&a)也會出現同樣的問題。

    解決方法:這種錯誤大多是輸入的時候不小心,多注意一點就好了。這種問題也不好檢查,編譯沒有問題,一個空格也不容易看出來。當你的程序出現上面的問題時,自己對照檢查一下就可以了。

    2.緩衝區問題這是一個非常容易錯的地方,我就錯過多次。

    #include <stdio.h> main()

    { int n = 5;char c[n];for(int i = 0; i < n; i++)

    c[i] = scanf("%c",&c[i]);printf(c);return 0;{如果輸入:a b c那麼循環就會“提前”結束了。

    原因:輸入a和第一個回車後,a和這個回車符都留在緩衝區中。第一個scanf讀取了a,但是輸入緩衝區裏面還留有一個/n,第二個scanf讀取這個/n.然後輸入b和第二個回車,同樣的,第三個scanf讀取了b,第四個scanf讀取了第二個回車符。第五個讀取了c.所以五個scanf都執行了,並沒有提前結束。只不過有的scanf讀取到了回車符而已。

    解決方法:把程序改成這樣就可以了:for( i = 0; i < n; i++){ scanf("%c",&c[i]);fflush(stdin);//刷新緩衝區}或者不用scanf,而用gets()函數,如:#include<stdio.h> main()

    { char c[5];gets(c);printf(c);return 0;}但要注意:這個函數自動把你最後敲的回車轉換爲字符'/0'.如果你的輸入超過了數組的大小,那麼就會產生錯誤。

    3.scanf()函數的參數輸入類型不匹配問題這是我在csdn論壇上見到的問題,這個錯誤有時候會讓人莫名其妙。

    #include<stdio.h> main()

    { int a=123;char c='t';printf("input/n");scanf("%d%c",&a,&c);printf("%d/n%c/n",a,c);return 0;}當輸入a 回車 後,會直接跳過下面2個scanf語句,直接輸出爲123 t原因:對於scanf("%d%c",&a,&c),scanf語句執行時,首先試圖從緩衝區中讀入一個%d類型的數據,如果和第一個參數匹配,則繼續從緩衝區中讀取數據和第二個參數進行匹配,依次進行下去,直到匹配完所有的參數;如果其中有一個參數不匹配,那就從這個地方跳出,忽略這個scanf後面所有的參數,而去執行下一條語句。

    可以用下面的程序驗證一下:#include <stdio.h> int main()

    { int a=123,b=1;char c='t';scanf("%d%d",&a,&b);scanf("%c",&c);printf("%d/n%d/n%c/n",a,b,c);return 0;}輸入:2 回車a 回車結果是:2 1 a解決方法:scanf()函數執行成功時的返回值是成功讀取的變量數,也就是說,你這個scanf()函數有幾個變量,如果scanf()函數全部正常讀取,它就返回幾。但這裏還要注意另一個問題,如果輸入了非法數據,鍵盤緩衝區就可能還個有殘餘信息問題。

    比如:#include <stdio.h> main()

    { int a=123,b;while(scanf)"%d%d",&a,&b(!=2)

    fflush(stdin);printf("%d/n%d/n",a,b);return 0;{你可以試一下,如果輸入不是數字時,會有什麼反應。

    補充:scanf中一種很少見但很有用的轉換字符:[……]和[ ^……]. #include<stdio.h> main()

    { char strings[100];scanf("%[1234567890]",strings);printf("%s",strings);return  0;}運行,輸入:1234werew後,結果是:1234.通過運行可以發現它的作用是:如果輸入的字符屬於方括號內字符串中某個字符,那麼就提取該字符;如果一經發現不屬於就結束提取。該方法會自動加上一個字符串結束符到已經提取的字符後面。

    scanf("%[^1234567890]",strings); 它的作用是:如果一經發現輸入的字符屬於方括號內字符串中某個字符,那麼就結束提取;如果不屬於就提取該字符。該方法會自動加上一個字符串結束符到已經提取的字符後面。

    注意:方括號兩邊不能空格,如:scanf("%[ 1234567890 ]",strings); scanf("%[ ^1234567890 ]",strings); 不讓空格也會算在裏面的。

    用這種方法還可以解決scanf的輸入中不能有空格的問題。只要用scanf("%[^/n]",strings); 就可以了。很神奇吧。

    scanf原型:參見《C語言大全》和K&C # include <stdio.h>;int scanf( const char *format, …… );函數 scanf() 是從標準輸入流 stdin 中讀內容的通用子程序,可以讀入全部固有類型的數據並自動轉換成機內形式。

    在 C99 中,format 用 restrict 修飾。

    format 指向的控制串由以下三類字符組成:● 格式說明符● 空白符● 非空白符

    轉換字符(就是%後跟的部分)

    a   讀浮點值(僅適用於 C99)

    A   讀浮點值(僅適用於 C99)

    c   讀單字符d   讀十進制整數i   讀十進制、八進制、十六進制整數e   讀浮點數E   讀浮點數f   讀浮點數F   讀浮點數(僅適用於 C99)

    g   讀浮點數G   讀浮點數o   讀八進制數s   讀字符串x   讀十六進制數X   讀十六進制數p   讀指針值n   至此已讀入值的等價字符數u   讀無符號十進制整數[ ]  掃描字符集合%   讀 % 符號(百分號)

    例如: %s 表示讀串而 %d 表示讀整數。格式串的處理順序爲從左到右,格式說明符逐一與變元表中的變元匹配。爲了讀取長整數,可以將 l(ell) 放在格式說明符的前面;爲了讀取短整數,可以將 h 放在格式說明符的前面。這些修飾符可以與 d、i、o、u 和 x 格式代碼一起使用。

    默認情況下,a、f、e 和 g 告訴 scanf() 爲 float 分配數據。 如果將 l(ell) 放在這些修飾符的前面,則 scanf() 爲 double 分配數據。使用 L 就是告訴 scanf(),接收數據的變量是 long double 型變量。

    如果使用的現代編譯器程序支持 1995 年增加的寬字符特性, 則可以與 c 格式代碼一起,用 l 修飾符說明類型 wchar_t 的寬字符指針;也可以與 s 格式代碼一起,用 l 修飾符說明寬字符串的指針。l 修飾符也可以用於修飾掃描集,以說明寬字符。

    控制串中的空白符使 scanf() 在輸入流中跳過一個或多個空白行。空白符可以是空格(space)、製表符(tab)和新行符(newline)。 本質上,控制串中的空白符使 scanf() 在輸入流中讀,但不保存結果,直到發現非空白字符爲止。

    非空白符使 scanf() 在流中讀一個匹配的字符並忽略之。例如,"%d,%d" 使 scanf() 先讀入一個整數,讀入中放棄逗號,然後讀另一個整數。如未發現匹配,scanf() 返回。

    scanf() 中用於保存讀入值的變元必須都是變量指針,即相應變量的地址。

    在輸入流中,數據項必須由空格、製表符和新行符分割。逗號和分號等不是分隔符,比如以下代碼:scanf( "%d %d", &r, &c );將接受輸入 10 20,但遇到 10,20 則失敗。

    百分號(%)與格式符之間的星號(*)表示讀指定類型的數據但不保存。因此,scanf( "%d %*c %d", &x, &y );對 10/20 的讀入操作中,10 放入變量 x,20 放入 y.

    格式命令可以說明最大域寬。 在百分號(%)與格式碼之間的整數用於限制從對應域讀入的最大字符數。例如,希望向 address 讀入不多於 20 個字符時,可以書寫成如下形式:scanf( "%20s", address );

    如果輸入流的內容多於 20 個字符,則下次 scanf() 從此次停止處開始讀入。 若達到最大域寬前已遇到空白符,則對該域的讀立即停止;此時,scanf() 跳到下一個域。

    雖然空格、製表符和新行符都用做域分割符號,但讀單字符操作中卻按一般字符處理。例如,對輸入流 "x y" 調用:scanf( "%c%c%c", &a, &b, &c );返回後,x 在變量 a 中,空格在變量 b 中,y 在變量 c 中。

    注意,控制串中的其它字符,包括空格、製表符和新行符,都用於從輸入流中匹配並放棄字符,被匹配的字符都放棄。例如,給定輸入流 "10t20",調用:scanf( "%dt%d", &x, &y );將把 10 和 20 分別放到 x 和 y 中,t 被放棄,因爲 t 在控制串中。

    ANSI C 標準向 scanf() 增加了一種新特性,稱爲掃描集(scanset)。 掃描集定義一個字符集合,可由 scanf() 讀入其中允許的字符並賦給對應字符數組。 掃描集合由一對方括號中的一串字符定義,左方括號前必須綴以百分號。 例如,以下的掃描集使 scanf() 讀入字符 A、B 和 C:%[ABC]

    使用掃描集時,scanf() 連續吃進集合中的字符並放入對應的字符數組,直到發現不在集合中的字符爲止(即掃描集僅讀匹配的字符)。返回時,數組中放置以 null 結尾、由讀入字符組成的字符串。

    用字符 ^ 可以說明補集。把 ^ 字符放爲掃描集的第一字符時,構成其它字符組成的命令的補集合,指示 scanf() 只接受未說明的其它字符。

    對於許多實現來說,用連字符可以說明一個範圍。 例如,以下掃描集使 scanf() 接受字母 A 到 Z:%[A-Z]重要的是要注意掃描集是區分大小寫的。因此,希望掃描大、小寫字符時,應該分別說明大、小寫字母。

    scanf() 返回等於成功賦值的域數的值,但由於星號修飾符而讀入未賦值的域不計算在內。給第一個域賦值前已出錯時,返回 EOF.

    C99 爲 scanf() 增加了幾個格式修飾符:hh、ll、j、z 和 t.hh 修飾符可用於 d、i、o、u、x、X 或 n.它說明相應的變元是 signed 或 unsigned char 值,或用於 n 時, 相應的變元是指向 long char 型變量的指針。ll 修飾符也可用於 d、i、o、u、x、X 或 n.它說明相應的變元是 signed 或者 unsigned long long int 值。

    j 格式修飾符應用於 d、i、o、u、x、X 或 n,說明匹配的變元是類型 intmax_t 或 uintmax_t.這些類型在 <stdint.h>; 中聲明,並說明最大寬度的整數。

    z 格式修飾符應用於 d、i、o、u、x、X 或 n,說明匹配的變元是指向 size_t 類型對象的指針。該類型在 <stddef.h>; 中聲明,並說明 sizeof 的結構。

    t 格式修飾符應用於 d、i、o、u、x、X 或 n,說明匹配的變元是指向 ptrdiff_t  類型對象的指針。該類型在 <stddef.h>; 中聲明,並說明兩個指針之間的差別。

    例子:

    # include <stdio.h>;int main( void )

    { char str[80], str2[80];int i;/* read a string and a integer */ scanf( "%s%d", str, &i );/* read up to 79 chars into str */ scanf( "%79s", str );/* skip the integer between the two strings */ scanf( "%s%*d%s", str, str2 );return 0;}

    發表於 @ 2006年03月29日 11:49 PM | 評論 (0)

    Turbo C 2.0 函數中文說明大全

    分類函數,所在函數庫爲ctype.h int isalpha(int ch) 若ch是字母('A'-'Z','a'-'z')返回非0值,否則返回0 int isalnum(int ch) 若ch是字母('A'-'Z','a'-'z')或數字('0'-'9'),返回非0值,否則返回0 int isascii(int ch) 若ch是字符(ASCII碼中的0-127)返回非0值,否則返回0 int iscntrl(int ch) 若ch是作廢字符(0x7F)或普通控制字符(0x00-0x1F),返回非0值,否則返回0 int isdigit(int ch) 若ch是數字('0'-'9')返回非0值,否則返回0 int isgraph(int ch) 若ch是可打印字符(不含空格)(0x21-0x7E)返回非0值,否則返回0 int islower(int ch) 若ch是小寫字母('a'-'z')返回非0值,否則返回0 int isprint(int ch) 若ch是可打印字符(含空格)(0x20-0x7E)返回非0值,否則返回0 int ispunct(int ch) 若ch是標點字符(0x00-0x1F)返回非0值,否則返回0 int isspace(int ch) 若ch是空格(' '),水平製表符('/t'),回車符('/r'), 走紙換行('/f'),垂直製表符('/v'),換行符('/n'), 返回非0值,否則返回0 int isupper(int ch) 若ch是大寫字母('A'-'Z')返回非0值,否則返回0 int isxdigit(int ch) 若ch是16進制數('0'-'9','A'-'F','a'-'f')返回非0值, 否則返回0 int tolower(int ch) 若ch是大寫字母('A'-'Z')返回相應的小寫字母('a'-'z')

    int toupper(int ch) 若ch是小寫字母('a'-'z')返回相應的大寫字母('A'-'Z')

    數學函數,所在函數庫爲math.h、stdlib.h、string.h、float.h int abs(int i) 返回整型參數i的絕對值double cabs(struct complex znum) 返回複數znum的絕對值double fabs(double x) 返回雙精度參數x的絕對值long labs(long n) 返回長整型參數n的絕對值double exp(double x) 返回指數函數ex的值double frexp(double value,int *eptr) 返回value=x*2n中x的值,n存貯在eptr中double ldexp(double value,int exp); 返回value*2exp的值double log(double x) 返回logex的值double log10(double x) 返回log10x的值double pow(double x,double y) 返回xy的值double pow10(int p) 返回10p的值double sqrt(double x) 返回x的開方double acos(double x) 返回x的反餘弦cos-1(x)值,x爲弧度double asin(double x) 返回x的反正弦sin-1(x)值,x爲弧度double atan(double x) 返回x的反正切tan-1(x)值,x爲弧度double atan2(double y,double x) 返回y/x的反正切tan-1(x)值,y的x爲弧度double cos(double x) 返回x的餘弦cos(x)值,x爲弧度double sin(double x) 返回x的正弦sin(x)值,x爲弧度double tan(double x) 返回x的正切tan(x)值,x爲弧度double cosh(double x) 返回x的雙曲餘弦cosh(x)值,x爲弧度double sinh(double x) 返回x的雙曲正弦sinh(x)值,x爲弧度double tanh(double x) 返回x的雙曲正切tanh(x)值,x爲弧度double hypot(double x,double y) 返回直角三角形斜邊的長度(z), x和y爲直角邊的長度,z2=x2+y2 double ceil(double x) 返回不小於x的最小整數double floor(double x) 返回不大於x的最大整數void srand(unsigned seed) 初始化隨機數發生器int rand() 產生一個隨機數並返回這個數double poly(double x,int n,double c[]) 從參數產生一個多項式double modf(double value,double *iptr) 將雙精度數value分解成尾數和階double fmod(double x,double y) 返回x/y的餘數double frexp(double value,int *eptr) 將雙精度數value分成尾數和階double atof(char *nptr) 將字符串nptr轉換成浮點數並返回這個浮點數double atoi(char *nptr) 將字符串nptr轉換成整數並返回這個整數double atol(char *nptr) 將字符串nptr轉換成長整數並返回這個整數char *ecvt(double value,int ndigit,int *decpt,int *sign)

    將浮點數value轉換成字符串並返回該字符串char *fcvt(double value,int ndigit,int *decpt,int *sign)

    將浮點數value轉換成字符串並返回該字符串char *gcvt(double value,int ndigit,char *buf)

    將數value轉換成字符串並存於buf中,並返回buf的指針char *ultoa(unsigned long value,char *string,int radix)

    將無符號整型數value轉換成字符串並返回該字符串,radix爲轉換時所用基數char *ltoa(long value,char *string,int radix)

    將長整型數value轉換成字符串並返回該字符串,radix爲轉換時所用基數char *itoa(int value,char *string,int radix)

    將整數value轉換成字符串存入string,radix爲轉換時所用基數double atof(char *nptr) 將字符串nptr轉換成雙精度數,並返回這個數,錯誤返回0 int atoi(char *nptr) 將字符串nptr轉換成整型數, 並返回這個數,錯誤返回0 long atol(char *nptr) 將字符串nptr轉換成長整型數,並返回這個數,錯誤返回0 double strtod(char *str,char **endptr)將字符串str轉換成雙精度數,並返回這個數,long strtol(char *str,char **endptr,int base)將字符串str轉換成長整型數, 並返回這個數,int matherr(struct exception *e) 用戶修改數學錯誤返回信息函數(沒有必要使用)

    double _matherr(_mexcep why,char *fun,double *arg1p, double *arg2p,double retval)

    用戶修改數學錯誤返回信息函數(沒有必要使用)

    unsigned int _clear87() 清除浮點狀態字並返回原來的浮點狀態void _fpreset() 重新初使化浮點數學程序包unsigned int _status87() 返回浮點狀態字

    目錄函數,所在函數庫爲dir.h、dos.h int chdir(char *path) 使指定的目錄path(如:"C://WPS")變成當前的工作目錄,成功返回0 int findfirst(char *pathname,struct ffblk *ffblk,int attrib)

    查找指定的文件,成功返回0 pathname爲指定的目錄名和文件名,如"C://WPS//TXT" ffblk爲指定的保存文件信息的一個結構,定義如下:┏━━━━━━━━━━━━━━━━━━┓┃struct ffblk ┃┃{ ┃┃ char ff_reserved[21]; /*DOS保留字*/┃┃ char ff_attrib; /*文件屬性*/ ┃┃ int ff_ftime; /*文件時間*/ ┃┃ int ff_fdate; /*文件日期*/ ┃┃ long ff_fsize; /*文件長度*/ ┃┃ char ff_name[13]; /*文件名*/ ┃┃} ┃┗━━━━━━━━━━━━━━━━━━┛attrib爲文件屬性,由以下字符代表┏━━━━━━━━━┳━━━━━━━━┓┃FA_RDONLY 只讀文件┃FA_LABEL 卷標號┃┃FA_HIDDEN 隱藏文件┃FA_DIREC 目錄 ┃┃FA_SYSTEM 系統文件┃FA_ARCH 檔案 ┃┗━━━━━━━━━┻━━━━━━━━┛例:struct ffblk ff;findfirst("*.wps",&ff,FA_RDONLY);

    int findnext(struct ffblk *ffblk) 取匹配finddirst的文件,成功返回0 void fumerge(char *path,char *drive,char *dir,char *name,char *ext)

    此函數通過盤符drive(C:、A:等), 路徑dir(/TC、/BC/LIB等), 文件名name(TC、WPS等),擴展名ext(。EXE、。COM等)組成一個文件名存與path中。

    int fnsplit(char *path,char *drive,char *dir,char *name,char *ext)

    此函數將文件名path分解成盤符drive(C:、A:等), 路徑dir(/TC、/BC/LIB等), 文件名name(TC、WPS等),擴展名ext(。EXE、。COM等),並分別存入相應的變量中。

    int getcurdir(int drive,char *direc)

    此函數返回指定驅動器的當前工作目錄名稱。成功返回0 drive 指定的驅動器(0=當前,1=A,2=B,3=C等)

    direc 保存指定驅動器當前工作路徑的變量char *getcwd(char *buf,iint n) 此函數取當前工作目錄並存入buf中,直到n個字節長爲爲止。錯誤返回NULL int getdisk() 取當前正在使用的驅動器,返回一個整數(0=A,1=B,2=C等)

    int setdisk(int drive) 設置要使用的驅動器drive(0=A,1=B,2=C等), 返回可使用驅動器總數int mkdir(char *pathname) 建立一個新的目錄pathname,成功返回0 int rmdir(char *pathname) 刪除一個目錄pathname,成功返回0 char *mktemp(char *template) 構造一個當前目錄上沒有的文件名並存於template中char *searchpath(char *pathname) 利用MSDOS找出文件filename所在路徑, 此函數使用DOS的PATH變量,未找到文件返回NULL

    進程函數,所在函數庫爲stdlib.h、process.h void abort() 此函數通過調用具有出口代碼3的_exit寫一個終止信息於stderr,並異常終止程序。無返回值

    int exec…裝入和運行其它程序int execl(char *pathname,char *arg0,char *arg1,…,char *argn,NULL)

    int execle(char *pathname,char *arg0,char *arg1,…, char *argn,NULL,char *envp[])

    int execlp(char *pathname,char *arg0,char *arg1,…,NULL)

    int execlpe(char *pathname,char *arg0,char *arg1,…,NULL,char *envp[])

    int execv(char *pathname,char *argv[])

    int execve(char *pathname,char *argv[],char *envp[])

    int execvp(char *pathname,char *argv[])

    int execvpe(char *pathname,char *argv[],char *envp[])

    exec函數族裝入並運行程序pathname,並將參數arg0(arg1,arg2,argv[],envp[])傳遞給子程序,出錯返回-1.在exec函數族中,後綴l、v、p、e添加到exec後,所指定的函數將具有某種操作能力。

    有後綴 p時,函數可以利用DOS的PATH變量查找子程序文件。

    l時,函數中被傳遞的參數個數固定。

    v時,函數中被傳遞的參數個數不固定。

    e時,函數傳遞指定參數envp,允許改變子進程的環境,無後綴 e時,子進程使用當前程序的環境。

    void _exit(int status) 終止當前程序,但不清理現場void exit(int status) 終止當前程序,關閉所有文件,寫緩衝區的輸出(等待輸出), 並調用任何寄存器的"出口函數",無返回值

    int spawn…運行子程序int spawnl(int mode,char *pathname,char *arg0,char *arg1,…, char *argn,NULL)

    int spawnle(int mode,char *pathname,char *arg0,char *arg1,…, char *argn,NULL,char *envp[])

    int spawnlp(int mode,char *pathname,char *arg0,char *arg1,…, char *argn,NULL)

    int spawnlpe(int mode,char *pathname,char *arg0,char *arg1,…, char *argn,NULL,char *envp[])

    int spawnv(int mode,char *pathname,char *argv[])

    int spawnve(int mode,char *pathname,char *argv[],char *envp[])

    int spawnvp(int mode,char *pathname,char *argv[])

    int spawnvpe(int mode,char *pathname,char *argv[],char *envp[])

    spawn函數族在mode模式下運行子程序pathname,並將參數arg0(arg1,arg2,argv[],envp[])傳遞給子程序。出錯返回-1 mode爲運行模式:mode爲 P_WAIT 表示在子程序運行完後返回本程序P_NOWAIT 表示在子程序運行時同時運行本程序(不可用)

    P_OVERLAY 表示在本程序退出後運行子程序

    在spawn函數族中,後綴l、v、p、e添加到spawn後,所指定的函數將具有某種操作能力有後綴 p時, 函數利用DOS的PATH查找子程序文件l時, 函數傳遞的參數個數固定。

    v時, 函數傳遞的參數個數不固定。

    e時, 指定參數envp可以傳遞給子程序,允許改變子程序運行環境。

    無後綴 e時,子程序使用本程序的環境。

    int system(char *command)

    將MSDOS命令command傳遞給DOS執行轉換子程序,函數庫爲math.h、stdlib.h、ctype.h、float.h char *ecvt(double value,int ndigit,int *decpt,int *sign)

    將浮點數value轉換成字符串並返回該字符串char *fcvt(double value,int ndigit,int *decpt,int *sign)

    將浮點數value轉換成字符串並返回該字符串char *gcvt(double value,int ndigit,char *buf)

    將數value轉換成字符串並存於buf中,並返回buf的指針char *ultoa(unsigned long value,char *string,int radix)

    將無符號整型數value轉換成字符串並返回該字符串,radix爲轉換時所用基數char *ltoa(long value,char *string,int radix)

    將長整型數value轉換成字符串並返回該字符串,radix爲轉換時所用基數char *itoa(int value,char *string,int radix)

    將整數value轉換成字符串存入string,radix爲轉換時所用基數double atof(char *nptr) 將字符串nptr轉換成雙精度數,並返回這個數,錯誤返回0 int atoi(char *nptr) 將字符串nptr轉換成整型數, 並返回這個數,錯誤返回0 long atol(char *nptr) 將字符串nptr轉換成長整型數,並返回這個數,錯誤返回0 double strtod(char *str,char **endptr)

    將字符串str轉換成雙精度數,並返回這個數,long strtol(char *str,char **endptr,int base)

    將字符串str轉換成長整型數, 並返回這個數,int toascii(int c) 返回c相應的ASCII int tolower(int ch) 若ch是大寫字母('A'-'Z')返回相應的小寫字母('a'- 'z')

    int _tolower(int ch) 返回ch相應的小寫字母('a'-'z')

    int toupper(int ch) 若ch是小寫字母('a'-'z')返回相應的大寫字母('A'- 'Z')

    int _toupper(int ch) 返回ch相應的大寫字母('A'-'Z')

    診斷函數,所在函數庫爲assert.h、math.h void assert(int test) 一個擴展成if語句那樣的宏,如果test測試失敗,就顯示一個信息並異常終止程序,無返回值void perror(char *string) 本函數將顯示最近一次的錯誤信息,格式如:字符串string:錯誤信息char *strerror(char *str) 本函數返回最近一次的錯誤信息,格式如: 字符串str:錯誤信息int matherr(struct exception *e)

    用戶修改數學錯誤返回信息函數(沒有必要使用)

    double _matherr(_mexcep why,char *fun,double *arg1p, double *arg2p,double retval)

    用戶修改數學錯誤返回信息函數(沒有必要使用)

    輸入輸出子程序, 函數庫爲io.h、conio.h、stat.h、dos.h、stdio.h、signal.h

    int kbhit() 本函數返回最近所敲的按鍵int fgetchar() 從控制檯(鍵盤)讀一個字符,顯示在屏幕上int getch() 從控制檯(鍵盤)讀一個字符,不顯示在屏幕上int putch() 向控制檯(鍵盤)寫一個字符int getchar() 從控制檯(鍵盤)讀一個字符,顯示在屏幕上int putchar() 向控制檯(鍵盤)寫一個字符int getche() 從控制檯(鍵盤)讀一個字符,顯示在屏幕上int ungetch(int c) 把字符c退回給控制檯(鍵盤)

    char *cgets(char *string) 從控制檯(鍵盤)讀入字符串存於string中int scanf(char *format[,argument…])

    從控制檯讀入一個字符串,分別對各個參數進行賦值,使用BIOS進行輸出int vscanf(char *format,Valist param)

    從控制檯讀入一個字符串,分別對各個參數進行賦值,使用BIOS進行輸出,參數從Valist param中取得int cscanf(char *format[,argument…])

    從控制檯讀入一個字符串,分別對各個參數進行賦值,直接對控制檯作操作,比如顯示器在顯示時字符時即爲直接寫頻方式顯示int sscanf(char *string,char *format[,argument,…])

    通過字符串string, 分別對各個參數進行賦值int vsscanf(char *string,char *format,Vlist param)

    通過字符串string,分別對各個參數進行賦值,參數從Vlist param中取得int puts(char *string) 發關一個字符串string給控制檯(顯示器), 使用BIOS進行輸出void cputs(char *string) 發送一個字符串string給控制檯(顯示器), 直接對控制檯作操作,比如顯示器即爲直接寫頻方式顯示int printf(char *format[,argument,…])

    發送格式化字符串輸出給控制檯(顯示器),使用BIOS進行輸出int vprintf(char *format,Valist param)

    發送格式化字符串輸出給控制檯(顯示器),使用BIOS進行輸出,參數從Valist param中取得int cprintf(char *format[,argument,…])

    發送格式化字符串輸出給控制檯(顯示器), 直接對控制檯作操作,比如顯示器即爲直接寫頻方式顯示int vcprintf(char *format,Valist param)

    發送格式化字符串輸出給控制檯(顯示器), 直接對控制檯作操作,比如顯示器即爲直接寫頻方式顯示, 參數從Valist param中取得int sprintf(char *string,char *format[,argument,…])

    將字符串string的內容重新寫爲格式化後的字符串int vsprintf(char *string,char *format,Valist param)

    將字符串string的內容重新寫爲格式化後的字符串,參數從Valist param中取得int rename(char *oldname,char *newname)將文件oldname的名稱改爲newname int ioctl(int handle,int cmd[,int *argdx,int argcx])

    本函數是用來控制輸入/輸出設備的,請見下表:┌───┬────────────────────────────┐│cmd值 │功能 │├───┼────────────────────────────┤│ 0 │取出設備信息 ││ 1 │設置設備信息 ││ 2 │把argcx字節讀入由argdx所指的地址 ││ 3 │在argdx所指的地址寫argcx字節 ││ 4 │除把handle當作設備號(0=當前,1=A,等)之外,均和cmd=2時一樣 ││ 5 │除把handle當作設備號(0=當前,1=A,等)之外,均和cmd=3時一樣 ││ 6 │取輸入狀態 ││ 7 │取輸出狀態 ││ 8 │測試可換性;只對於DOS 3.x ││ 11 │置分享衝突的重算計數;只對DOS 3.x │└───┴────────────────────────────┘int (*ssignal)int sig,int(*action)()()( 執行軟件信號)沒必要使用(

    int gsignal(int sig) 執行軟件信號(沒必要使用)

    int _open(char *pathname,int access)爲讀或寫打開一個文件, 按後按access來確定是讀文件還是寫文件,access值見下表┌──────┬────────────────────┐│access值 │意義 │├──────┼────────────────────┤│O_RDONLY │讀文件 ││O_WRONLY │寫文件 ││O_RDWR │即讀也寫 ││O_NOINHERIT │若文件沒有傳遞給子程序,則被包含 ││O_DENYALL │只允許當前處理必須存取的文件 ││O_DENYWRITE │只允許從任何其它打開的文件讀 ││O_DENYREAD │只允許從任何其它打開的文件寫 ││O_DENYNONE │允許其它共享打開的文件 │└──────┴────────────────────┘int open(char *pathname,int access[,int permiss])爲讀或寫打開一個文件, 按後按access來確定是讀文件還是寫文件,access值見下表┌────┬────────────────────┐│access值│意義 │├────┼────────────────────┤│O_RDONLY│讀文件 ││O_WRONLY│寫文件 ││O_RDWR │即讀也寫 ││O_NDELAY│沒有使用;對UNIX系統兼容 ││O_APPEND│即讀也寫,但每次寫總是在文件尾添加 ││O_CREAT │若文件存在,此標誌無用;若不存在,建新文件 ││O_TRUNC │若文件存在,則長度被截爲0,屬性不變 ││O_EXCL │未用;對UNIX系統兼容 ││O_BINARY│此標誌可顯示地給出以二進制方式打開文件 ││O_TEXT │此標誌可用於顯示地給出以文本方式打開文件│└────┴────────────────────┘permiss爲文件屬性,可爲以下值:S_IWRITE允許寫 S_IREAD允許讀 S_IREAD|S_IWRITE允許讀、寫int creat(char *filename,int permiss) 建立一個新文件filename,並設定讀寫性。

    permiss爲文件讀寫性,可以爲以下值S_IWRITE允許寫 S_IREAD允許讀 S_IREAD|S_IWRITE允許讀、寫int _creat(char *filename,int attrib) 建立一個新文件filename,並設定文件屬性。

    attrib爲文件屬性,可以爲以下值FA_RDONLY只讀 FA_HIDDEN隱藏 FA_SYSTEM系統int creatnew(char *filenamt,int attrib) 建立一個新文件filename,並設定文件屬性。

    attrib爲文件屬性,可以爲以下值FA_RDONLY只讀 FA_HIDDEN隱藏 FA_SYSTEM系統int creattemp(char *filenamt,int attrib) 建立一個新文件filename,並設定文件屬性。

    attrib爲文件屬性,可以爲以下值FA_RDONLY只讀 FA_HIDDEN隱藏 FA_SYSTEM系統int read(int handle,void *buf,int nbyte) 從文件號爲handle的文件中讀nbyte個字符存入buf中int _read(int handle,void *buf,int nbyte) 從文件號爲handle的文件中讀nbyte個字符存入buf中,直接調用MSDOS進行操作。

    int write(int handle,void *buf,int nbyte) 將buf中的nbyte個字符寫入文件號爲handle的文件中int _write(int handle,void *buf,int nbyte) 將buf中的nbyte個字符寫入文件號爲handle的文件中int dup(int handle) 複製一個文件處理指針handle,返回這個指針int dup2(int handle,int newhandle) 複製一個文件處理指針handle到newhandle int eof(int *handle) 檢查文件是否結束,結束返回1,否則返回0 long filelength(int handle) 返回文件長度,handle爲文件號int setmode(int handle,unsigned mode)本函數用來設定文件號爲handle的文件的打開方式int getftime(int handle,struct ftime *ftime)

    讀取文件號爲handle的文件的時間,並將文件時間存於ftime結構中,成功返回0, ftime結構如下:┌─────────────────┐│struct ftime ││{ ││ unsigned ft_tsec:5; /*秒*/ ││ unsigned ft_min:6; /*分*/ ││ unsigned ft_hour:5; /*時*/ ││ unsigned ft_day:5; /*日*/ ││ unsigned ft_month:4;/*月*/ ││ unsigned ft_year:1; /*年-1980*/ ││} │└─────────────────┘int setftime(int handle,struct ftime *ftime) 重寫文件號爲handle的文件時間,新時間在結構ftime中。成功返回0.結構ftime如下:┌─────────────────┐│struct ftime ││{ ││ unsigned ft_tsec:5; /*秒*/ ││ unsigned ft_min:6; /*分*/ ││ unsigned ft_hour:5; /*時*/ ││ unsigned ft_day:5; /*日*/ ││ unsigned ft_month:4;/*月*/ ││ unsigned ft_year:1; /*年-1980*/ ││} │└─────────────────┘long lseek(int handle,long offset,int fromwhere)

    本函數將文件號爲handle的文件的指針移到fromwhere後的第offset個字節處。

    SEEK_SET文件開關 SEEK_CUR當前位置 SEEK_END文件尾long tell(int handle) 本函數返回文件號爲handle的文件指針,以字節表示int isatty(int handle)本函數用來取設備handle的類型int lock(int handle,long offset,long length) 對文件共享作封鎖int unlock(int handle,long offset,long length) 打開對文件共享的封鎖

    int close(int handle) 關閉handle所表示的文件處理,handle是從_creat、creat、creatnew、creattemp、dup、dup2、_open、open中的一個處調用獲得的文件處理成功返回0否則返回-1,可用於UNIX系統int _close(int handle) 關閉handle所表示的文件處理,handle是從_creat、creat、creatnew、creattemp、dup、dup2、_open、open中的一個處調用獲得的文件處理成功返回0否則返回-1,只能用於MSDOS系統

    FILE *fopen(char *filename,char *type) 打開一個文件filename,打開方式爲type,並返回這個文件指針,type可爲以下字符串加上後綴┌──┬────┬───────┬────────┐│type│讀寫性 │文本/2進制文件│建新/打開舊文件 │├──┼────┼───────┼────────┤│r │讀 │文本 │打開舊的文件 ││w │寫 │文本 │建新文件 ││a │添加 │文本 │有就打開無則建新││r+ │讀/寫 │不限制 │打開 ││w+ │讀/寫 │不限制 │建新文件 ││a+ │讀/添加 │不限制 │有就打開無則建新│└──┴────┴───────┴────────┘可加的後綴爲t、b.加b表示文件以二進制形式進行操作,t沒必要使用例: ┌──────────────────┐│#include<stdio.h> ││main() ││{ ││ FILE *fp; ││ fp=fopen("C://WPS//WPS.EXE","r+b");│└──────────────────┘FILE *fdopen(int ahndle,char *type)

    FILE *freopen(char *filename,char *type,FILE *stream)

    int getc(FILE *stream) 從流stream中讀一個字符,並返回這個字符int putc(int ch,FILE *stream) 向流stream寫入一個字符ch int getw(FILE *stream) 從流stream讀入一個整數,錯誤返回EOF int putw(int w,FILE *stream) 向流stream寫入一個整數int ungetc(char c,FILE *stream) 把字符c退回給流stream,下一次讀進的字符將是c int fgetc(FILE *stream) 從流stream處讀一個字符,並返回這個字符int fputc(int ch,FILE *stream) 將字符ch寫入流stream中char *fgets(char *string,int n,FILE *stream)

    從流stream中讀n個字符存入string中int fputs(char *string,FILE *stream)將字符串string寫入流stream中int fread(void *ptr,int size,int nitems,FILE *stream)

    從流stream中讀入nitems個長度爲size的字符串存入ptr中int fwrite(void *ptr,int size,int nitems,FILE *stream)

    向流stream中寫入nitems個長度爲size的字符串,字符串在ptr中int fscanf(FILE *stream,char *format[,argument,…])

    以格式化形式從流stream中讀入一個字符串int vfscanf(FILE *stream,char *format,Valist param)

    以格式化形式從流stream中讀入一個字符串,參數從Valist param中取得int fprintf(FILE *stream,char *format[,argument,…])

    以格式化形式將一個字符串寫給指定的流stream int vfprintf(FILE *stream,char *format,Valist param)

    以格式化形式將一個字符串寫給指定的流stream,參數從Valist param中取得int fseek(FILE *stream,long offset,int fromwhere)

    函數把文件指針移到fromwhere所指位置的向後offset個字節處,fromwhere可以爲以下值:SEEK_SET 文件開關 SEEK_CUR 當前位置 SEEK_END 文件尾long ftell(FILE *stream) 函數返回定位在stream中的當前文件指針位置,以字節表示int rewind(FILE *stream) 將當前文件指針stream移到文件開頭int feof(FILE *stream) 檢測流stream上的文件指針是否在結束位置int fileno(FILE *stream) 取流stream上的文件處理,並返回文件處理int ferror(FILE *stream) 檢測流stream上是否有讀寫錯誤,如有錯誤就返回1 void clearerr(FILE *stream) 清除流stream上的讀寫錯誤void setbuf(FILE *stream,char *buf) 給流stream指定一個緩衝區buf void setvbuf(FILE *stream,char *buf,int type,unsigned size)

    給流stream指定一個緩衝區buf,大小爲size,類型爲type,type的值見下表

    ┌───┬───────────────────────────────┐│type值│意義 │├───┼───────────────────────────────┤│_IOFBF│文件是完全緩衝區,當緩衝區是空時,下一個輸入操作將企圖填滿整個緩││ │衝區。在輸出時,在把任何數據寫到文件之前,將完全填充緩衝區。 ││_IOLBF│文件是行緩衝區。當緩衝區爲空時,下一個輸入操作將仍然企圖填整個緩││ │衝區。然而在輸出時,每當新行符寫到文件,緩衝區就被清洗掉。 ││_IONBF│文件是無緩衝的。buf和size參數是被忽略的。每個輸入操作將直接從文 ││ │件讀,每個輸出操作將立即把數據寫到文件中。 │└───┴───────────────────────────────┘int fclose(FILE *stream) 關閉一個流,可以是文件或設備(例如LPT1)

    int fcloseall() 關閉所有除stdin或stdout外的流int fflush(FILE *stream)

    關閉一個流,並對緩衝區作處理處理即對讀的流,將流內內容讀入緩衝區;對寫的流,將緩衝區內內容寫入流。成功返回0 int fflushall()

    關閉所有流,並對流各自的緩衝區作處理處理即對讀的流,將流內內容讀入緩衝區;對寫的流,將緩衝區內內容寫入流。成功返回0 int access(char *filename,int amode)

    本函數檢查文件filename並返回文件的屬性, 函數將屬性存於amode中,amode由以下位的組合構成06可以讀、寫 04可以讀 02可以寫 01執行(忽略的) 00文件存在如果filename是一個目錄,函數將只確定目錄是否存在函數執行成功返回0,否則返回-1 int chmod(char *filename,int permiss) 本函數用於設定文件filename的屬性permiss可以爲以下值S_IWRITE允許寫 S_IREAD允許讀 S_IREAD|S_IWRITE允許讀、寫int _chmod(char *filename,int func[,int attrib]);本函數用於讀取或設定文件filename的屬性,當func=0時,函數返回文件的屬性;當func=1時,函數設定文件的屬性若爲設定文件屬性,attrib可以爲下列常數之一FA_RDONLY只讀 FA_HIDDEN隱藏 FA_SYSTEM系統

    接口子程序,所在函數庫爲os.h、bios.h unsigned sleep(unsigned seconds) 暫停seconds微秒(百分之一秒)

    int unlink(char *filename) 刪除文件filename unsigned FP_OFF(void far *farptr) 本函數用來取遠指針farptr的偏移量unsigned FP_SEG(void far *farptr) 本函數用來沒置遠指針farptr的段值void far *MK_FP(unsigned seg,unsigned off)根據段seg和偏移量off構造一個far指針unsigned getpsp() 取程序段前綴的段地址,並返回這個地址char *parsfnm(char *cmdline,struct fcb *fcbptr,int option)

    函數分析一個字符串,通常,對一個文件名來說,是由cmdline所指的一個命令行。

    文件名是放入一個FCB中作爲一個驅動器,文件名和擴展名。FCB是由fcbptr所指定的。

    option參數是DOS分析系統調用時,AL文本的值。

    int absread(int drive,int nsects,int sectno,void *buffer)

    本函數功能爲讀特定的磁盤扇區,drive爲驅動器號(0=A,1=B等),nsects爲要讀的扇區數,sectno爲開始的邏輯扇區號,buffer爲保存所讀數據的保存空間int abswrite(int drive,int nsects,int sectno,void *buffer)

    本函數功能爲寫特定的磁盤扇區,drive爲驅動器號(0=A,1=B等),nsects爲要寫的扇區數,sectno爲開始的邏輯扇區號,buffer爲保存所寫數據的所在空間void getdfree(int drive,struct dfree *dfreep)

    本函數用來取磁盤的自由空間,drive爲磁盤號(0=當前,1=A等)。函數將磁盤特性的由dfreep指向的dfree結構中。 dfree結構如下:┌───────────────────┐│struct dfree ││{ ││ unsigned df_avail; /*有用簇個數*/ ││ unsigned df_total; /*總共簇個數*/ ││ unsigned df_bsec; /*每個扇區字節數*/││ unsigned df_sclus; /*每個簇扇區數*/ ││} │└───────────────────┘char far *getdta() 取磁盤轉換地址DTA void setdta(char far *dta) 設置磁盤轉換地址DTA void getfat(int drive,fatinfo *fatblkp)

    本函數返回指定驅動器drive(0=當前,1=A,2=B等)的文件分配表信息並存入結構fatblkp中,結構如下:┌──────────────────┐│struct fatinfo ││{ ││ char fi_sclus; /*每個簇扇區數*/ ││ char fi_fatid; /*文件分配表字節數*/││ int fi_nclus; /*簇的數目*/ ││ int fi_bysec; /*每個扇區字節數*/ ││} │└──────────────────┘void getfatd(struct fatinfo *fatblkp) 本函數返回當前驅動器的文件分配表信息, 並存入結構fatblkp中,結構如下:┌──────────────────┐│struct fatinfo ││{ ││ char fi_sclus; /*每個簇扇區數*/ ││ char fi_fatid; /*文件分配表字節數*/││ int fi_nclus; /*簇的數目*/ ││ int fi_bysec; /*每個扇區字節數*/ ││} │└──────────────────┘int bdos(int dosfun,unsigned dosdx,unsigned dosal)

    本函數對MSDOS系統進行調用, dosdx爲寄存器dx的值,dosal爲寄存器al的值,dosfun爲功能號int bdosptr(int dosfun,void *argument,unsiigned dosal)

    本函數對MSDOS系統進行調用,argument爲寄存器dx的值,dosal爲寄存器al的值,dosfun爲功能號int int86(int intr_num,union REGS *inregs,union REGS *outregs)

    執行intr_num號中斷,用戶定義的寄存器值存於結構inregs中, 執行完後將返回的寄存器值存於結構outregs中。

    int int86x(int intr_num,union REGS *inregs,union REGS *outregs, struct SREGS *segregs)

    執行intr_num號中斷,用戶定義的寄存器值存於結構inregs中和結構segregs中,執行完後將返回的寄存器值存於結構outregs中。

    int intdos(union REGS *inregs,union REGS *outregs)

    本函數執行DOS中斷0x21來調用一個指定的DOS函數,用戶定義的寄存器值存於結構inregs中,執行完後函數將返回的寄存器值存於結構outregs中int intdosx(union REGS *inregs,union REGS *outregs,struct SREGS *segregs)

    本函數執行DOS中斷0x21來調用一個指定的DOS函數,用戶定義的寄存器值存於結構inregs和segregs中,執行完後函數將返回的寄存器值存於結構outregs中void intr(int intr_num,struct REGPACK *preg)

    本函數中一個備用的8086軟件中斷接口它能產生一個由參數intr_num指定的8086軟件中斷。

    函數在執行軟件中斷前, 從結構preg複製用戶定義的各寄存器值到各個寄存器。軟件中斷完成後,函數將當前各個寄存器的值複製到結構preg中。參數如下:intr_num 被執行的中斷號,preg爲保存用戶定義的寄存器值的結構,結構如下┌──────────────────────┐│struct REGPACK ││{ ││ unsigned r_ax,r_bx,r_cx,r_dx; ││ unsigned r_bp,r_si,r_di,r_ds,r_es,r_flags; ││} │└──────────────────────┘函數執行完後,將新的寄存器值存於結構preg中void keep(int status,int size)

    以status狀態返回MSDOS,但程序仍保留於內存中,所佔用空間由size決定。

    void ctrlbrk(int )*fptr()() 設置中斷後的對中斷的處理程序。

    void disable() 禁止發生中斷void enable() 允許發生中斷void geninterrupt(int intr_num) 執行由intr_num所指定的軟件中斷void interrupt(* getvect)int intr_num()()

    返回中斷號爲intr_num的中斷處理程序, 例如: old_int_10h=getvect(0x10);void setvect(int intr_num,void interrupt)* isr()()

    設置中斷號爲intr_num的中斷處理程序爲isr,例如: setvect(0x10,new_int_10h);void harderr(int )*fptr()()

    定義一個硬件錯誤處理程序, 每當出現錯誤時就調用fptr所指的程序void hardresume(int rescode) 硬件錯誤處理函數void hardretn(int errcode) 硬件錯誤處理函數int inport(int prot) 從指定的輸入端口讀入一個字,並返回這個字int inportb(int port) 從指定的輸入端口讀入一個字節,並返回這個字節void outport(int port,int word) 將字word寫入指定的輸出端口port void outportb(int port,char byte) 將字節byte寫入指定的輸出端口port int peek(int segment,unsigned offset)

    函數返回segmentffset處的一個字char peekb(int segment,unsigned offset)

    函數返回segmentffset處的一個字節void poke(int segment,int offset,char value)

    將字value寫到segmentffset處void pokeb(int segment,int offset,int value)

    將字節value寫到segmentffset處int randbrd(struct fcb *fcbptr,int reccnt)

    函數利用打開fcbptr所指的FCB讀reccnt個記錄。

    int randbwr(struct fcb *fcbptr,int reccnt)

    函數將fcbptr所指的FCB中的reccnt個記錄寫到磁盤上void segread(struct SREGS *segtbl)函數把段寄存器的當前值放進結構segtbl中int getverify() 取檢驗標誌的當前狀態(0=檢驗關閉,1=檢驗打開)

    void setverify(int value)

    設置當前檢驗狀態, value爲0表示關閉檢驗,爲1表示打開檢驗int getcbrk()本函數返回控制中斷檢測的當前設置int setcbrk(int value)本函數用來設置控制中斷檢測爲接通或斷開當value=0時,爲斷開檢測。當value=1時,爲接開檢測int dosexterr(struct DOSERR *eblkp)

    取擴展錯誤。在DOS出現錯誤後,此函數將擴充的錯誤信息填入eblkp所指的DOSERR結構中。該結構定義如下:┌──────────────┐│struct DOSERR ││{ ││ int exterror;/*擴展錯誤*/ ││ char class; /*錯誤類型*/ ││ char action; /*方式*/ ││ char locus; /*錯誤場所*/ ││} │└──────────────┘int bioscom(int cmd,char type,int port) 本函數負責對數據的通訊工作,cmd可以爲以下值:0 置通訊參數爲字節byte值 1 發送字符通過通訊線輸出2 從通訊線接受字符 3 返回通訊的當前狀態port爲通訊端口,port=0時通訊端口爲COM1,port=1時通訊端口爲COM2,以此類推byte爲傳送或接收數據時的參數,爲以下位的組合:┌───┬─────┬───┬─────┬───┬─────┐│byte值│意義 │byte值│意義 │byte值│意義 │├───┼─────┼───┼─────┼───┼─────┤│0x02 │7數據位 │0x03 │8數據位 │0x00 │1停止位 ││0x04 │2停止位 │0x00 │無奇偶性 │0x08 │奇數奇偶性││0x18 │偶數奇偶性│0x00 │110波特 │0x20 │150波特 ││0x40 │300波特 │0x60 │600波特 │0x80 │1200波特 ││0xA0 │2400波特 │0xC0 │4800波特 │0xE0 │9600波特 │└───┴─────┴───┴─────┴───┴─────┘

    例如:0xE0|0x08|0x00|0x03即表示置通訊口爲9600波特,奇數奇偶性,1停止位,8數據位。 函數返回值爲一個16位整數,定義如下:第15位 超時第14位 傳送移位寄存器空第13位 傳送固定寄存器空第12位 中斷檢測第11位 幀錯誤第10位 奇偶錯誤第 9位 過載運行錯誤第 8位 數據就緒第 7位 接收線信號檢測第 6位 環形指示器第 5位 數據設置就緒第 4位 清除發送第 3位 δ接收線信號檢測器第 2位 下降邊環形檢測器第 1位 δ數據設置就緒第 0位 δ清除發送

    int biosdisk(int cmd,int drive,int head,int track, int sector,int nsects,void *buffer)

    本函數用來對驅動器作一定的操作,cmd爲功能號, drive爲驅動器號(0=A,1=B,0x80=C,0x81=D,0x82=E等)。cmd可爲以下值:

    0 重置軟磁盤系統。這強迫驅動器控制器來執行硬復位。忽略所有其它參數。

    1 返回最後的硬盤操作狀態。忽略所有其它參數2 讀一個或多個磁盤扇區到內存。讀開始的扇區由head、track、sector給出。扇區號由nsects給出。把每個扇區512個字節的數據讀入buffer 3 從內存讀數據寫到一個或多個扇區。寫開始的扇區由head、track、sector給出。扇區號由nsects給出。所寫數據在buffer中,每扇區512個字節。

    4 檢驗一個或多個扇區。開始扇區由head、track、sector給出。扇區號由nsects給出。

    5 格式化一個磁道,該磁道由head和track給出。buffer指向寫在指定track上的扇區磁頭器的一個表。以下cmd值只允許用於XT或AT微機:6 格式化一個磁道,並置壞扇區標誌。

    7 格式化指定磁道上的驅動器開頭。

    8 返回當前驅動器參數,驅動器信息返回寫在buffer中(以四個字節表示)。

    9 初始化一對驅動器特性。

    10 執行一個長的讀,每個扇區讀512加4個額外字節11 執行一個長的寫,每個扇區寫512加4個額外字節12 執行一個磁盤查找13 交替磁盤復位14 讀扇區緩衝區15 寫扇區緩衝區16 檢查指定的驅動器是否就緒17 複覈驅動器18 控制器RAM診斷19 驅動器診斷20 控制器內部診

    函數返回由下列位組合成的狀態字節:0x00 操作成功0x01 壞的命令0x02 地址標記找不到0x04 記錄找不到0x05 重置失敗0x07 驅動參數活動失敗0x09 企圖DMA經過64K界限0x0B 檢查壞的磁盤標記0x10 壞的ECC在磁盤上讀0x11 ECC校正的數據錯誤(注意它不是錯誤)

    0x20 控制器失效0x40 查找失敗0x80 響應的連接失敗0xBB 出現無定義錯誤0xFF 讀出操作失敗

    int biodquip()

    檢查設備,函數返回一字節,該字節每一位表示一個信息,如下:第15位 打印機號第14位 打印機號第13位 未使用第12位 連接遊戲I/O第11位 RS232端口號第 8位 未使用第 7位 軟磁盤號第 6位 軟磁盤號,00爲1號驅動器,01爲2號驅動器,10爲3號驅動器,11爲4號驅動器

    第 5位 初始化第 4位 顯示器模式00爲未使用,01爲40x25BW彩色顯示卡10爲80x25BW彩色顯示卡,11爲80x25BW單色顯示卡第 3位 母扦件第 2位 隨機存貯器容量,00爲16K,01爲32K,10爲48K,11爲64K第 1位 浮點共用處理器第 0位 從軟磁盤引導

    int bioskey(int cmd)本函數用來執行各種鍵盤操作,由cmd確定操作。

    cmd可爲以下值:0 返回敲鍵盤上的下一個鍵。若低8位爲非0,即爲ASCII字符;若低8位爲0,則返回擴充了的鍵盤代碼。

    1 測試鍵盤是否可用於讀。返回0表示沒有鍵可用;否則返回下一次敲鍵之值。

    敲鍵本身一直保持由下次調用具的cmd值爲0的bioskey所返回的值。

    2 返回當前的鍵盤狀態,由返回整數的每一個位表示,見下表:┌──┬───────────┬───────────┐│ 位 │爲0時意義 │爲1時意義 │├──┼───────────┼───────────┤│ 7 │插入狀態 │改寫狀態 ││ 6 │大寫狀態 │小寫狀態 ││ 5 │數字狀態,NumLock燈亮 │光標狀態,NumLock燈熄 ││ 4 │ScrollLock燈亮 │ScrollLock燈熄 ││ 3 │Alt按下 │Alt未按下 ││ 2 │Ctrl按下 │Ctrl未按下 ││ 1 │左Shift按下 │左Shift未按下 ││ 0 │右Shift按下 │右Shift未按下 │└──┴───────────┴───────────┘int biosmemory() 返回內存大小,以K爲單位。

    int biosprint(int cmd,int byte,int port) 控制打印機的輸入/輸出。

    port爲打印機號,0爲LPT1,1爲LPT2,2爲LPT3等cmd可以爲以下值:0 打印字符,將字符byte送到打印機1 打印機端口初始化2 讀打印機狀態函數返回值由以下位值組成表示當前打印機狀態0x01 設備時間超時0x08 輸入/輸出錯誤0x10 選擇的0x20 走紙0x40 認可0x80 不忙碌

    int biostime(int cmd,long newtime)計時器控制,cmd爲功能號,可爲以下值

    0 函數返回計時器的當前值1 將計時器設爲新值newtime

    struct country *country(int countrycmode,struct country *countryp)

    本函數用來控制某一國家的相關信息,如日期,時間,貨幣等。

    若countryp=-1時,當前的國家置爲countrycode值(必須爲非0)。否則,由countryp所指向的country結構用下列的國家相關信息填充:(1)當前的國家(若countrycode爲0或2)由countrycode所給定的國家。

    結構country定義如下:┌────────────────────┐│struct country ││{ ││ int co_date; /*日期格式*/ ││ char co_curr[5]; /*貨幣符號*/ ││ char co_thsep[2]; /*數字分隔符*/ ││ char co_desep[2]; /*小數點*/ ││ char co_dtsep[2]; /*日期分隔符*/ ││ char co_tmsep[2]; /*時間分隔符*/ ││ char co_currstyle; /*貨幣形式*/ ││ char co_digits; /*有效數字*/ ││ int (far *co_case)(); /*事件處理函數*/ ││ char co_dasep; /*數據分隔符*/ ││ char co_fill[10]; /*補充字符*/ ││} │└────────────────────┘co_date的值所代表的日期格式是:0 月日年 1 日月年 2 年月日co_currstrle的值所代表的貨幣顯示方式是0 貨幣符號在數值前,中間無空格1 貨幣符號在數值後,中間無空格2 貨幣符號在數值前,中間有空格3 貨幣符號在數值後,中間有空格

    操作函數,所在函數庫爲string.h、mem.h mem…操作存貯數組void *memccpy(void *destin,void *source,unsigned char ch,unsigned n)

    void *memchr(void *s,char ch,unsigned n)

    void *memcmp(void *s1,void *s2,unsigned n)

    int memicmp(void *s1,void *s2,unsigned n)

    void *memmove(void *destin,void *source,unsigned n)

    void *memcpy(void *destin,void *source,unsigned n)

    void *memset(void *s,char ch,unsigned n)

    這些函數,mem…系列的所有成員均操作存貯數組。在所有這些函數中,數組是n字節長。

    memcpy從source複製一個n字節的塊到destin.如果源塊和目標塊重迭,則選擇複製方向, 以例正確地複製覆蓋的字節。

    memmove與memcpy相同。 memset將s的所有字節置於字節ch中。s數組的長度由n給出。

    memcmp比較正好是n字節長的兩個字符串s1和s2.些函數按無符號字符比較字節,因此,memcmp("0xFF","/x7F",1)返回值大於0. memicmp比較s1和s2的前n個字節,不管字符大寫或小寫。

    memccpy從source複製字節到destin.複製一結束就發生下列任一情況:(1)字符ch首選複製到destin.(2)n個字節已複製到destin. memchr對字符ch檢索s數組的前n個字節。

    返回值:memmove和memcpy返回destin memset返回s的值memcmp和memicmp─┬─若s1<s2返回值小於0├─若s1=s2返回值等於0└─若s1>s2返回值大於0 memccpy若複製了ch,則返回直接跟隨ch的在destin中的字節的一個指針;

    否則返回NULL memchr返回在s中首先出現ch的一個指針;如果在s數組中不出現ch,就返回NULL.

    void movedata(int segsrc,int offsrc, int segdest,int offdest, unsigned numbytes)

    本函數將源地址(segsrcffsrc)處的numbytes個字節複製到目標地址(segdestffdest)

    void movemem(void *source,void *destin,unsigned len)

    本函數從source處複製一塊長len字節的數據到destin.若源地址和目標地址字符串重迭,則選擇複製方向,以便正確的複製數據。

    void setmem(void *addr,int len,char value)

    本函數把addr所指的塊的第一個字節置於字節value中。

    str…字符串操作函數char stpcpy(char *dest,const char *src) 將字符串src複製到dest char strcat(char *dest,const char *src) 將字符串src添加到dest末尾char strchr(const char *s,int c) 檢索並返回字符c在字符串s中第一次出現的位置int strcmp(const char *s1,const char *s2) 比較字符串s1與s2的大小,並返回s1-s2 char strcpy(char *dest,const char *src) 將字符串src複製到dest size_t strcspn(const char *s1,const char *s2) 掃描s1,返回在s1中有,在s2中也有的字符個數char strdup(const char *s) 將字符串s複製到最近建立的單元int stricmp(const char *s1,const char *s2) 比較字符串s1和s2,並返回s1-s2 size_t strlen(const char *s) 返回字符串s的長度char strlwr(char *s)

    將字符串s中的大寫字母全部轉換成小寫字母,並返回轉換後的字符串char strncat(char *dest,const char *src,size_t maxlen)

    將字符串src中最多maxlen個字符複製到字符串dest中int strncmp(const char *s1,const char *s2,size_t maxlen)

    比較字符串s1與s2中的前maxlen個字符char strncpy(char *dest,const char *src,size_t maxlen)

    複製src中的前maxlen個字符到dest中int strnicmp(const char *s1,const char *s2,size_t maxlen)

    比較字符串s1與s2中的前maxlen個字符char strnset(char *s,int ch,size_t n)

    將字符串s的前n個字符置於ch中char strpbrk(const char *s1,const char *s2)

    掃描字符串s1,並返回在s1和s2中均有的字符個數char strrchr(const char *s,int c)

    掃描最後出現一個給定字符c的一個字符串s char strrev(char *s)

    將字符串s中的字符全部顛倒順序重新排列,並返回排列後的字符串char strset(char *s,int ch)

    將一個字符串s中的所有字符置於一個給定的字符ch size_t strspn(const char *s1,const char *s2)

    掃描字符串s1,並返回在s1和s2中均有的字符個數char strstr(const char *s1,const char *s2)

    掃描字符串s2,並返回第一次出現s1的位置char strtok(char *s1,const char *s2)

    檢索字符串s1,該字符串s1是由字符串s2中定義的定界符所分隔char strupr(char *s)

    將字符串s中的小寫字母全部轉換成大寫字母,並返回轉換後的字符串

    存貯分配子程序,所在函數庫爲dos.h、alloc.h、malloc.h、stdlib.h、process.h int allocmem(unsigned size,unsigned *seg)

    利用DOS分配空閒的內存, size爲分配內存大小,seg爲分配後的內存指針int freemem(unsigned seg)

    釋放先前由allocmem分配的內存,seg爲指定的內存指針int setblock(int seg,int newsize)

    本函數用來修改所分配的內存長度, seg爲已分配內存的內存指針,newsize爲新的長度int brk(void *endds)

    本函數用來改變分配給調用程序的數據段的空間數量,新的空間結束地址爲endds char *sbrk(int incr)

    本函數用來增加分配給調用程序的數據段的空間數量,增加incr個字節的空間unsigned long coreleft() 本函數返回未用的存儲區的長度,以字節爲單位void *calloc(unsigned nelem,unsigned elsize)

    分配nelem個長度爲elsize的內存空間並返回所分配內存的指針void *malloc(unsigned size) 分配size個字節的內存空間,並返回所分配內存的指針void free(void *ptr) 釋放先前所分配的內存,所要釋放的內存的指針爲ptr void *realloc(void *ptr,unsigned newsize)

    改變已分配內存的大小,ptr爲已分配有內存區域的指針,newsize爲新的長度,返回分配好的內存指針。

    long farcoreleft() 本函數返回遠堆中未用的存儲區的長度,以字節爲單位void far *farcalloc(unsigned long units,unsigned long unitsz)

    從遠堆分配units個長度爲unitsz的內存空間,並返回所分配內存的指針void *farmalloc(unsigned long size)

    分配size個字節的內存空間, 並返回分配的內存指針void farfree(void far *block)

    釋放先前從遠堆分配的內存空間, 所要釋放的遠堆內存的指針爲block void far *farrealloc(void far *block,unsigned long newsize)

    改變已分配的遠堆內存的大小,block爲已分配有內存區域的指針,newzie爲新的長度,返回分配好的內存指針

    時間日期函數,函數庫爲time.h、dos.h在時間日期函數裏,主要用到的結構有以下幾個:總時間日期貯存結構tm┌──────────────────────┐│struct tm ││{ ││ int tm_sec; /*秒,0-59*/ ││ int tm_min; /*分,0-59*/ ││ int tm_hour; /*時,0-23*/ ││ int tm_mday; /*天數,1-31*/ ││ int tm_mon; /*月數,0-11*/ ││ int tm_year; /*自1900的年數*/ ││ int tm_wday; /*自星期日的天數0-6*/ ││ int tm_yday; /*自1月1日起的天數,0-365*/ ││ int tm_isdst; /*是否採用夏時制,採用爲正數*/││} │└──────────────────────┘日期貯存結構date┌───────────────┐│struct date ││{ ││ int da_year; /*自1900的年數*/││ char da_day; /*天數*/ ││ char da_mon; /*月數 1=Jan*/ ││} │└───────────────┘時間貯存結構time┌────────────────┐│struct time ││{ ││ unsigned char ti_min; /*分鐘*/││ unsigned char ti_hour; /*小時*/││ unsigned char ti_hund; ││ unsigned char ti_sec; /*秒*/ ││ │└────────────────┘char *ctime(long *clock)

    本函數把clock所指的時間(如由函數time返回的時間)轉換成下列格式的字符串:Mon Nov 21 11:31:54 1983/n/0 char asctime(struct tm *tm)

    本函數把指定的tm結構類的時間轉換成下列格式的字符串:Mon Nov 21 11:31:54 1983/n/0 double difftime(time_t time2,time_t time1)

    計算結構time2和time1之間的時間差距(以秒爲單位)

    struct tm *gmtime(long *clock)

    本函數把clock所指的時間(如由函數time返回的時間)轉換成格林威治時間,並以tm結構形式返回struct tm *localtime(long *clock)

    本函數把clock所指的時間(如函數time返回的時間)轉換成當地標準時間,並以tm結構形式返回void tzset()本函數提供了對UNIX操作系統的兼容性long dostounix(struct date *dateptr,struct time *timeptr)

    本函數將dateptr所指的日期,timeptr所指的時間轉換成UNIX格式, 並返回自格林威治時間1970年1月1日凌晨起到現在的秒數void unixtodos(long utime,struct date *dateptr,struct time *timeptr)

    本函數將自格林威治時間1970年1月1日凌晨起到現在的秒數utime轉換成DOS格式並保存於用戶所指的結構dateptr和timeptr中void getdate(struct date *dateblk)

    本函數將計算機內的日期寫入結構dateblk中以供用戶使用void setdate(struct date *dateblk)

    本函數將計算機內的日期改成由結構dateblk所指定的日期void gettime(struct time *timep)

    本函數將計算機內的時間寫入結構timep中, 以供用戶使用void settime(struct time *timep)

    本函數將計算機內的時間改爲由結構timep所指的時間long time(long *tloc)

    本函數給出自格林威治時間1970年1月1日凌晨至現在所經過的秒數,並將該值存於tloc所指的單元中。

    int stime(long *tp)本函數將tp所指的時間(例如由time所返回的時間)寫入計算機中

發佈了43 篇原創文章 · 獲贊 5 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章