下载

1下载券

加入VIP
  • 专属下载特权
  • 现金文档折扣购买
  • VIP免费专区
  • 千万文档免费下载

上传资料

关闭

关闭

关闭

封号提示

内容

首页 《Linux程序设计》学习笔记

《Linux程序设计》学习笔记.doc

《Linux程序设计》学习笔记

369844372
2011-06-11 0人阅读 举报 0 0 0 暂无简介

简介:本文档为《《Linux程序设计》学习笔记doc》,可适用于IT/计算机领域

​ 《Linux程序设计》学习笔记入门什么是LinuxLinux是一个类UNIX内核的可以自由发布的实现版本是一个操作系统的底层核心。几乎所有为UNIX编写的程序都可以在Linux上编译运行。Linux是由赫尔辛基大学的LinusTorvalds开发的期间得到了因特网上广大UNIX程序员的帮助。它最初只是受AndyTanenbaum教授的Minix(一个小型类UNIX系统)启发而开发的个人爱好的程序但后来逐步发展成为一个拥有自己版权的完整系统。其目的是保证Linux除包含自由发布的代码外不会集成任何私有代码。GNU项目和自由软件基金会Linux社团支持自由软件的概念即软件本身不应受限它们应该遵守GNU通用公共许可证(GPL)。虽然获得软件可能要支付一定的费用但此后就可以随意使用并且它们通常是以源代码的形式发布的。在GPL规则限制下所有基于这种概念开发的软件都应遵循GPL。大家可以在http:wwwgnuorg上找到更多关于自由软件的概念。Linux程序Linux应用程序表现为两种特殊类型的文件:可执行文件和脚本文件。可执行文件是计算机可以直接运行的程序它们相当于Windows中的exe文件。脚本文件是一组指令的集合这些指令将由另一个程序(解释器比如shell或者perl)来执行它们相当于Windows中的bat文件、cmd文件或解释执行的BASIC程序。与Windows相比Linux程序并不要求可执行程序或脚本具有特殊的文件名或扩展名。当登录Linux系统时我们与一个shell程序(通常是bash)进行交互它像Windows中的命令提示窗口一样运行程序。在当前环境下必定有一组环境变量与之匹配其中PATH变量指明了当前可以自动搜索的目录:当需要执行的程序在PATH指定的目录中时你将不需指明待执行程序的全路径(除非有同名程序存在)否则必须指定需要执行程序的路径(相对路径或者绝对路径)。如果你还是Linux环境下的初学者一定要注意Linux使用正斜线()分割文件名里的目录名而不是像Windows那样使用反斜线()。建议:如果你刚刚接触Linux请先学会使用Linux再来学习如何在Linux环境下编程。C语言编译器这里我们使用GNUC编译器简称为gcc。因为它随Linux的发行版一起提供并且它支持ANSIC的标准语法。在http:wwwgnuorg上可以获取gcc软件包。建议:如果你是初学C语言请编写经典的HelloWorld程序来开始你的学习之旅。开发系统导引对Linux开发人员来说了解软件工具和开发资源在系统中存放的位置是很重要的。应用程序应用程序通常存放在系统为之保留的特定目录中。系统为正常使用提供的程序包括用于程序开发的工具都可以在目录usrbin中找到系统管理员为某个特定的主机或本地网络添加的程序通常可在目录usrlocalbin或opt中找到。建议:在usrlocal目录结构下编译、运行自己的程序并访问必须的文件。头文件对C语言来说头文件几乎总是在usrinclude目录及其子目录下。那些依赖于特定Linux版本的头文件通常可以在目录usrincludesys和usrincludelinux中找到。在调用C语言编译器时我们可以使用I标志来包含保存在子目录或非标准位置中的头文件比如:$gcc–Iusropenwinincludefredc。它指示编译器不仅在标准位置也在usropenwininclude目录中查找fredc中包含的头文件。提示:可以使用grep命令来搜索包含某些特定定义和函数原型的头文件。库文件库是一组预先编译好的函数的集合这些函数都是按照可以重用的原则编写的。标准库文件一般存储在lib和usrlib目录中。库文件的名字总是以lib开头随后的部分指明这是什么库(比如libm就代表了数学库)。文件名的最后部分以开始然后给出库文件的类型:a代表传统的静态函数库so代表共享函数库。提示:如何编写及使用两种函数库可以参看《精通UNIX下C语言编程及项目实践》的学习笔记​ 《Linux程序设计》学习笔记Shell程序设计管道和重定向重定向分为输入重定向’<’、输出重定向’>’和附加输出重定向’>>’。提示:默认情况下如果使用>操作符把输出重定向到一个文件而该文件已经存在时它的内容将被覆盖如果想改变该默认行为可以使用set–C命令设置noclobber选项。提示:可以使用UNIX的通用“回收站”dev来有效的丢弃输出信息比如:$kill–l>dev>我们可以使用管道操作符|来连接进程。Linux和MSDOS不同在Linux下通过管道连接的进程可以同时运行并且随着数据流在它们之间的传递可以自动地进行协调。举个例子:$grep–lPOSIX*|more它将输出包含POSIX字符串的文件名。实际上上述命令还有另外两种编写方式:$more`grep–lPOSIX*`或者$more$(grep–lPOSIX*)Shell脚本程序Shell脚本非常灵活在Linux的使用中往往经常使用它来完成某些繁琐但又必须的工作。鉴于本笔记主要针对Linux下的C语言编程这里并不会用太多的篇幅来介绍Shell脚本。运行脚本程序有两个办法:一种是在命令行上直接输入命令PATH=$PATH:或者编辑bashprofile文件将刚才的命令添加在文件的末尾然后退出登录再重新登录进来另一种就是在保存脚本程序的目录中先键入再输入脚本命令如此做的作用是把脚本程序的完整的相对路径告诉Shell。建议:考虑到系统的安全性最好的办法是在当前目录中的所有命令前都加上一个。推荐:如果你想深入地学习使用Shell可以去ChinaUnix的Shell子论坛。Find命令只要你是在UNIX或者类UNIX环境下一个必不可少的命令就是find命令。它的功能是查找文件比如说我们在编译某个C程序时发现错误提示说它不识别STDIN那么很明显的你的C程序缺少某个头文件但是如何才能知道它是在哪个头文件中定义的呢?一个非常有效地办法就是结合grep使用find命令:$findusrinclude–name“*h”|xargsgrep“STDIN”上面命令先在usrinclude目录下搜索所有包含了h的文件继而使用grep命令对这些头文件查找STDIN字符串。当然你可能说我知道STDIN定义在<stdioh>不用这么麻烦!但在实际的研发工作中我们经常会在某个并不熟悉的环境(比如设备驱动程序的开发)下编写程序我们会不可避免的查找某个函数或者宏定义的来源。提示:find命令是一个非常复杂的命令。它有很多的选项如果你想深入的了解它一个有效地途径就是使用man文档。很多情况下比如不熟悉的命令或者函数都可以从man文档中得到解答。​ 《Linux程序设计》学习笔记文件操作在Linux中一切(或几乎一切)都是文件。文件和设备硬件设备在Linux操作系统中通常被映射为文件。可以使用mount命令加载CDROM、Windows下的文件系统或者其他的设备。UNIX和Linux中比较重要的设备文件有三个:devconsole该设备代表系统控制台错误信息和诊断信息通常会被发送到这个设备上。在现代的工作站和Linux上它通常是“活跃”的虚拟控制台而在X窗口系统中它会是屏幕上一个特殊的控制台窗口。devtty如果一个进程有控制终端的话那么特殊文件devtty就是这个控制终端(键盘、显示屏或者窗口)的别名(逻辑设备)。注意:虽然devconsole设备只有一个但通过devtty却能够访问许多不同的物理设备。dev这是空设备所有写向该设备的输出都将被丢弃。提示:还有一个特殊设备devzero经常被用到它的作用是以内容为字节的源文件来来创建零长度文件。它经常用在dd命令的if参数中。扫描目录与目录操作相关的函数在direnth头文件中声明。其中目录的读写函数在《UNIX环境下C语言编程及项目实践》的读书笔记中有过介绍。除了opendir、closedir和readdir这三个常见的函数外还有两个函数:telldir和seekdir。telldir函数的返回值记录了一个目录流里的当前位置接着我们可以在随后的seekdir调用中利用这个值将目录指针重置到之前的位置。在Linux系统上一个常见的问题就是对目录进行扫描也就是确定一个特定目录下存放的文件。在上述提到的读书笔记中曾给出了一个扫描实例但是该实例只是扫描了当前目录下的文件并没有深入到子目录的层面。这里给出一个扫描当前目录下(包含子目录)所有文件的实例printdir**function:printallfilesanddirectoriesunder'dir'directory*paremeters:*dirinpointtothedirectoryneededtoprint*depthinwidthaligntoleft,incrementingwhilenested*voidprintdir(char*dir,intdepth){DIR*dpstructdirent*entrystructstatstatbufif((dp=opendir(dir))==){fprintf(stderr,"Can'topendirentory:sn",dir)return}chdir(dir)while((entry=readdir(dp))!=){lstat(entry>dname,statbuf)if(SISDIR(statbufstmode)){if(strcmp("",entry>dname)==||strcmp("",entry>dname)==)continueprintf("*ssn",depth,"",entry>dname)printdir(entry>dname,depth)}elseprintf("*ssn",depth,"",entry>dname)}chdir("")closedir(dp)}提示:为了在输出时对于不同层次的目录有缩进这里使用了可变字段宽度*s。其中*可以由一个整形值来指定代表了在输出后面字符串时所要求的宽度。proc文件系统Linux提供了一个特殊的文件系统procfs它通常表现为proc目录。该目录中包含了许多特殊文件以允许对驱动和内核信息进行高层访问。只要应用程序有正确的访问权限它们就可以通过读写这些文件来获得信息或设置参数。proc目录中的文件会随系统的不同而不同当Linux版本中有更多的驱动和设施支持procfs文件系统时该目录中就会包含更多的文件。不过该目录下有许多东西是在任何Linux系统中都存在的。大多情况下只需要直接读取这些文件就可以获取信息。比如proccpuinfo、procmeminfo、procversion和procnetsockstat就分别给出了CPU、内存、Linux版本和网络套接字的信息。其实proc目录下的有些文件不但可以读取还可以修改。比如说系统中所有运行的程序同时能够打开的文件总数是Linux内核的一个参数。它的值可以从procsysfsfilemax文件得到同样地你也可以直接修改该文件来更改可以直接打开的文件总数。提示:对proc目录中文件进行写操作需要超级用户的权限。在修改数据时一定要小心写入不当的值很可能会导致严重的后果。proc目录中还有一类文件以数字命名。比如当我们使用ps命令查看当前正在运行的进程时会显示每个进程的PID。每个进程都会对应proc目录下一个以该pid值命名的文件。如果你要查看该进程的具体信息可以直接读取文件proc(pid)。在列出的文件中cmdline文件会显示该进程由谁启动的你可以使用cat命令或者od命令来查看。​ 《Linux程序设计》学习笔记Linux环境当Linux编写程序时必须考虑到程序将在一个多任务环境中运行。这意味着在同一时间会有多个程序运行它们共享内存、磁盘空间和CPU周期等机器资源。甚至同一程序也会有多个实例同时运行。最重要的是这些程序能够互不干扰了解他们的环境并且能正确运行以避免冲突例如试图与其他程序同时写同一个文件。程序参数无论操作系统何时启动新程序参数argc和argv都被设置并传递给main。这些参数通常由其他程序提供这个程序一般是shell它请求操作系统启动该新程序。注意:Linux的shell一般会在设置argc和argv之前对文件名参数进行通配符扩展而MSDOS的shell则期望程序接受带通配符的参数并执行它们自己的通配符扩展。命令行参数在向程序传递信息方面是很有用的。许多工具程序都是有命令行参数来改变程序的行为或设置选项。这些参数大多以短横线()开头不带后续参数的选项还可在一个短横线后归并到一起。当需要在自己的程序中处理这些命令行参数时我们需要自己对这些参数解析从而判断出有效的参数。当参数个数很多时这一过程将是非常繁琐的。实际上XOpen规范定义了命令行选项的标准用法同时定义了在C语言程序中提供命令行开关的标准编程接口:getopt函数。getopt函数将传递给main程序的argc和argv作为参数同时接受一个选项指定字符串optstring该字符串高速getopt哪些选项可用以及每个选项是否有关联值。程序将循环调用getopt对选项参数进行处理直到getopt返回时处理完毕。关于getopt的详细信息请查阅man手册。我们在实际使用中可能会选择长参数它以双短横线()表示。GNUC库包含了getopt的另一个版本称为getoptlong它能同时接受短参数和长参数。getoptlong函数比getopt函数多了两个参数一个被定义为option结构(它指定了函数可以接受的长参数和函数对应返回的值)另一个通常被置。提示:在使用getoptlong函数时除了要包含头文件getopth外还需要把常量GNUSOURCE一同包含进来。无参数和void参数在定义自己的程序时当不需要传递参数时我们可能置参数列表为空或者填入void。那么这两种方式相同么?考虑下面两个函数voidfoo()voidfoo(void)当我们使用foo(,)调用foo函数时编译器将不报任何错误同时可以正常运行它的执行结果与调用foo()的结果一致。然而当我们使用foo(,)调用foo函数时编译器将报错。因此无参数和void参数使得函数显现出两种不同的行为。实际上void参数指定函数在调用时不能有任何参数而无参数则没有对参数的传递做任何的规范也就是说你可以传递任何类型的任意个数的参数。环境变量环境变量是一把双刃剑使用它的时候要小心!与命令行选项相比它们对用户来说更加“隐蔽”这样就使得调试变得更加困难。从某种意义上来说环境变量就像全局变量一样它们会改变程序的行为产生不可预期的结果。对于环境环境变量的读写操作可以有两种方式在《Unix环境下C语言编程及项目实践》的读书笔记中有详细的介绍。时间和日期通常能确定时间和日期对一个程序来说是非常有用的。我们可以使用time函数获取一个timet类型的时间值该值是从格林威治时间到当前时间点的秒数。函数localtime将一个timet类型的时间值转换为tm结构通过该结构可以清晰得了解当前的年、月、日、时、分等。函数mktime的功能则相反它将一个tm结构的时间值转换为timet类型。上面的几个函数在《精通Unix下C语言编程及项目实践》的读书笔记中有详细的介绍。为了得到更“友好”的时间和日期表示像date命令输出的那样我们可以使用asctime函数和ctime函数。实际上为了对时间和日期字符串的格式有更多的控制Linux和现代的类UNIX系统提供了strftime和strptime函数。它很像是一个针对时间和日期的sprintf和sscanf函数具体的信息请查看man手册。注意:编译包含了strptime函数的程序时需要在包含timeh头文件的语句之前包含XOPENSOURCE宏定义。提示:我们在程序中经常使用sleep函数来完成指定时间的睡眠。实际上sleep函数只能指定秒级的时间如果要精确到微妙级(s)可以使用usleep函数。除此之外还有一个timeval结构可以完成微妙级的操作。临时文件很多情况下程序会利用一些文件形式的临时存储手段。这些临时文件可能保存着一个计算的中间结果也可能是关键操作的文件备份。临时文件的用法很常见但必须确保应用程序为临时文件选取的文件名是唯一的。GNUC提供了两个函数tmpname和tmpfile来创建临时文件。详细情况请参考man手册。提示:当需要创建临时文件且需要对其进行读写时请优先考虑使用tmpfile函数。该函数同时创建和打开临时文件这样就避免了使用tmpnam函数时可能有另一个程序用同样的名字打开文件的风险。用户及主机信息程序能够通过检查环境变量和读取系统时钟来在很大程度上了解它所处的运行环境。除此之外程序还可以发现它的使用者的相关信息。函数getuid和getlogin分别获取程序关联的UID和与该关联ID相应的登录名。实际上系统文件etcpasswd包含了一个用户账户数据库。要获取某个用户的信息UNIX系统并不推荐直接对该系统文件读写它定义了一组函数来提供一个标准二又有效地获取用户信息的编程接口getpwuid和getpwnam。它们均返回一个指向与某个用户对应的passwd结构指针。提示:你可以对程序进行设置让它们的运行看上去好像是由另一个用户启动的。当一个程序的SUID权限被置位时它的运行就好像是由该可执行文件的属主启动的。一个典型的例子是su命令。在UNIX环境下可以使用uname系统调用来获取主机信息。它将主机信息写入一个utsname结构。详细细节请查阅man手册。日志许多应用程序需要记录它们的活动。系统程序经常需要向控制台或日志文件写消息。这些消息能指示错误、警告或是与系统状态有关的一般信息。提示:当程序短小时我们经常使用gdb工具来调试但当程序比较庞大时一个有效地调试手段就是使用日志功能在日志中搜索出错信息。UNIX规范为所有程序提供了一个接口通过syslog函数来产生日志信息。这些信息往往被记录在日志文件varlogmessage中部分调试信息可能记录在varlogdebug中。通过syslog函数向系统的日志文件发送的每条日志信息都有一个优先级。而且不同程序写入的日志信息并不能明显地区分开来。实际上UNIX提供了几个函数来改变日志记录行为:openlog、closelog和setlogmask函数。有效地使用这三个函数可以在日志文件中与其他程序写入的日志区分开。关于详细信息请查阅man手册。​ 《Linux程序设计》学习笔记终端对终端进行读写在编写程序时我们往往需要从终端读入数据。一种情况是需要连续地读入用户键入的选择项这往往出现在数据库程序中。程序员往往会使用getchar函数来读取数据继而判断输入的数据是否有效从而做出反应。其实如此做带有很大的风险一个实例程序如下#include<stdioh>char*menu={"aaddnewrecord","ddeleterecord","qquit",}intgetchoice(char*choices){intchosen=intselectedchar**optiondo{option=choiceswhile(*option){printf("sn",*option)option}selected=getchar()option=choiceswhile(*option){if(option==selected){chosen=break}option}if(!chosen){printf("Incorrectchoice,selectagainn")}}while(!chosen)returnselected}intmain(){intchoice=do{choice=getchoice(menu)printf("Youchoosecn",choice)}while('q'!=choice)exit()}实例程序中用户需要键入“A回车Q回车”才能做出选择。但这种处理有着很大的风险读者可以自己测试一下。这也是初学者经常碰到的问题。默认情况下只有当用户按下回车键后程序才能读到终端的输入。这种处理方式是规范模式或标准模式。在这种模式下所有的输入都给予行进行处理在一个输入行完成前终端接口负责管理所有的用户键盘输入包括退格键应用程序读不到用户输入的任何字符。与标准模式相对的另一种模式为非标准模式这种模式下应用程序对用户输入字符的处理拥有更大的控制权。在上述程序中Linux会暂存用户读入的内容直到用户按下回车键然后将用户选择的字符及紧随其后的回车符一起传送到程序。所以每当你选择一个菜单时程序就调用getchar函数处理该字符而当程序在下一次循环再次调用getchar函数时它会立刻返回一个回车符。一个解决方案是程序在每次读入数据前首先清空回车键之前的所有数据典型代码如下:do{selected=getchar()}while('n'!=selected)终端驱动程序和通用终端接口有时程序需要更加精细的终端控制能力而不是仅通过简单的文件操作来完成对终端的一些控制。Linux提供了一组编程接口这使得我们能够控制终端驱动程序的行为从而允许我们对终端的输入和输出进行更好的控制。有一组函数调用(GTI)用作控制终端这组函数调用与用于读写数据的函数是分离的这就使得读写数据的接口非常简洁同时又保证用户可以对终端的行为进行更精细的控制。termios结构通过设置termios结构中的值和使用一组函数调用我们就可以对终端接口进行控制。提示:使用termios结构及相关的函数调用需要包含termiosh头文件同时需要包含curses函数库。控制终端的操作模式有以下几种:输入模式、输出模式、控制模式、本地模式和特殊的控制字符。具体操作由tcgetattr函数和tcsetattr函数来完成。其中本地模式是最常用也是最重要的一种操作模式。注意:程序要将终端设置恢复到程序开始运行之前的状态这一点是非常重要的。首先保存这些值然后在程序结束时恢复它们这永远是程序的职责。输入模式控制输入数据在被传递给程序之前的处理方式。通过设置termios结构中的ciflag成员的标志对它们进行控制。输出模式控制输出字符的处理方式即由程序发送出去的字符在传递到串行口或屏幕之前是如何处理的。通过设置termios结构中coflag成员的标志对输出模式进行控制。控制模式控制终端的硬件特性。通过设置termios结构中的ccflag成员的标志对控制模式进行配置。控制模式主要用于串行线连接调制解调器的情况。本地模式控制终端的各种特性。通过设置termios结构中的clflag成员的标志对本地模式进行配置。其中最常用的两个标志是ECHO和ICANON。前者抑制键入字符的回显后者将终端在两个截然不同的接收字符处理模式之间进行切换。如果设置了ICANON标志就启用标准输入行处理模式否则就启动非标准模式。当用户键入类似CtrlC这样的组合键时终端会采取一些特殊的处理方式。termios结构中的ccc数组成员将各种特殊的控制字符映射到对应的支持函数。每个字符的位置是由一个宏定义的但不限制这些字符必须是控制字符。注意:在两种不同的模式(标准模式和非标准模式)下ccc数组的下标值有一部分是重叠的。出于这个原因一定要注意不要将两种模式各自的下标值混淆。可以通过stty命令查询及修改终端模式。通过termios结构我们还可以控制终端的传入和传出的速度(波特率)。终端的输出编写能够应付连接到UNIX系统上的各种不同类型终端的程序看上去是一件非常让人畏惧的事情。因为这样的程序必须针对各种类型的终端编写相应的代码。termifo软件包的出现解决了这一问题。在绝大多数现代的UNIX系统上这个软件包和另一个软件包curses集成在一起。注意:在Linux系统上在使用termifo软件包时可能需要包含ncurses库该库实现了curses软件包的所有功能。termifo的功能标识由属性描述它们被保存在一组编译好的terminfo文件中而这些文件可以方便地在usrlibterinfo或usrshareterinfo目录下找到。例如VT终端的定义就放在文件usrshareterminfovvt中。你可以使用infocmp程序输出terminfo编译数据项的可读版本。虚拟控制台在Linux的典型安装中将配置个虚拟控制台。虚拟控制台通过字符设备文件devttyN使用tty是Teletype的缩写而N代表一个数字从开始。通过who和ps命令可以查看目前登录进系统的用户以及目前在使用的虚拟控制台及其上运行的shell和程序。Linux系统一般在前六个虚拟控制台上运行一个getty进程这样用户即可用同一个屏幕、键盘和鼠标在六个不同的虚拟控制台上登录。可以通过组合键CtrlAltF<N>在这六个不同的虚拟控制台之间进行切换。如果Linux系统使用的是图形登录界面或者使用startx切入图形界面X视窗系统将使用第一个未使用的控制台通常是devtty。而伪终端由字符设备文件devpty使用其中pty是pseudotty的缩写。它与tty终端的区别在于伪终端没有对于的硬件设备。运程登录的终端由字符设备文件devptsN使用。​ 《Linux程序设计》学习笔记curses函数库Curses标准作为过渡位于简单的文本行程序和完全图形化界面(一般也更难于编程)的X视窗系统程序(如GTKGNOME和QtKDE)之间。Curses函数库的名称来自它所提供的功能它能够优化光标的移动并减少需要对屏幕进行的刷新因此它也减少了必须向字符终端发送的字符数目。基本使用方法Curses例程工作在屏幕、窗口和子窗口上。所谓“屏幕”就是正在写的设备(通常是终端屏幕也有可能是xterm屏幕)。Curses函数库使用两个数据结构来映射终端屏幕它们是stdscr和curscr。其中stdscr数据结构对应的是“标准屏幕”它的工作原理和stdio函数库中的标准输出stdout非常相似它是curses程序中的默认输出插口而curscr数据结构和stdscr相似但它对应的是当前屏幕的样子。一个使用curses函数库的典型例程如下:#include<unistdh>#include<stdlibh>#include<cursesh>intmain(){initscr()move(,)printw("s","HelloWorld!")refresh()endwin()exit()}当对使用curses函数库的程序进行编译时必须在程序中包含头文件cursesh它是需要在编译命令行中用lcurses选项对curses函数库进行链接。从上面的程序可以看到所有curses程序必须以初始化函数initscr开始以函数endwin结束。函数initscr在一个程序中只能调用一次。提示:我们可以先调用endwin函数退出curses然后通过调研clrearok(strscr,)和refresh函数继续curses操作。这样实际上是首先让curses忘记物理屏幕的样子然后强迫它执行一次完整的屏幕原文重现。函数move和printw的功能是移动光标和在当前位置上输出文本。在调用refresh函数之前输出到stdscr上的内容是不会显示在屏幕上的。refresh函数的作用就是刷新物理屏幕。当需要在屏幕上显示比较松散的多行文本时典型方式就是通过move函数与printw函数的配合来完成。简单来说Curses函数库有几种函数:屏幕输出函数、输入函数、清除函数和光标移动函数。通过这几种函数的配合我们就可以实现一个简单的全屏界面。字符属性:每个curses字符都可以有特定的属性该属性控制着该字符在屏幕上的显示方式前提是用于显示的硬件设备能够支持要求的属性。预定义的属性有ABLINK、ABOLD、ADIM、AREVERSE、ASTANDOUT和AUNDERLINE。相关函数有attron、attroff和attrset等。一个典型的使用片段如下:move(,)attron(ABOLD)printw("s","HelloWorld!")attroff(ABOLD)refresh()键盘:curses函数库还提供了控制键盘的简单方法。通过调用两个echo函数我们可以简单地关闭或开启输入字符的回显功能。通过调用break函数可以将输入模式设置为字符中止模式在这种模式下字符一经键入立刻传递给程序而不是像在行模式中那样首先缓存字符知道用户按下回车键才将用户输入的字符传递给程序。通过调用两个raw函数则可以关闭或开启特殊字符的处理。提示:curses环境下输入模式分行模式和字符中止模式。默认输入模式是行模式当用户键入回车符时才会将输入的数据传递给程序而字符中止模式则当字符一经键入就传递给程序。窗口与子窗口Curses函数库在物理屏幕上能够同时显示多个不同尺寸的窗口。在curses环境下窗口由WINDOW数据结构来表示。实际上标准屏幕stdscr只是WINDOW结构的一个特例。下面是一个使用了窗口的例程#include<unistdh>#include<stdlibh>#include<cursesh>intmain(){WINDOW*newwindowintx,ycharletter='a'initscr()for(y=y<LINESy)for(x=x<COLSx){mvwaddch(stdscr,y,x,letter)if(letter>'z')letter='a'}refresh()sleep()newwindow=newwin(,,,)box(newwindow,'|','')mvwprintw(newwindow,,,"s","HelloWorld")wrefresh(newwindow)sleep()mvwin(newwindow,,)wrefresh(newwindow)sleep()delwin(newwindow)endwin()return}上面的例程中先在全屏幕上填满字符然后创建一个*的新窗口继而在新窗口上输出“HelloWorld”。新窗口的建立是由newwin函数来实现的它指定了新窗口的大小和位置。删除一个窗口时则使用delwin函数。函数box的作用在于使用特殊的字符来界定新窗口。用于窗口的通用函数有几类:前缀w用于窗口、前缀mv用于光标移动、前缀mvw用于在制定窗口中移动光标。wrefresh函数用于刷新窗口。而mvwin函数的作用是移动指定的窗口到指定的位置如果移动后窗口超出屏幕范围mvwin函数调用将会失败。子窗口是多窗口的一种特例我们使用subwin函数和delwin函数创建和删除子窗口。与前面提到的新窗口相比子窗口没有自己独立的屏幕字符存储空间它们与它们的父窗口(在调用subwin时指定)共享同一字符存储空间。这意味着对子窗口中内容的任何修改都会反映到它的父窗口中所以删除子窗口时屏幕不会发生任何变化。子窗口主要的用途是提供了一种简洁的方式来卷动另一窗口里的部分内容。在编写curses程序时经常需要卷动屏幕的某个小区域将这个小区域定义为一个子窗口然后对其卷动就能达到我们想要的效果。注意:使用子窗口有个强加的限制:在应用程序刷新屏幕之前必须先对其父窗口调用touchwin函数。keypad模式curses函数库提供了一个精巧的用于管理功能键的功能。对每个终端来说它的每个功能键所对应的转义序列都被保存通常是保存在一个terminfo结构中而头文件cursesh通过一组以KEY为前缀的定义来管理逻辑键。curses在启动时会关闭转义序列与逻辑键之间的转换功能这功能需要通过调用keypad函数来启用。实际上使用keypad模式还是有一定的限制的:)识别escape转义序列的过程是与时间相关的。在处理许多网络协议时这个问题会变得很突出唯一解决办法是设法对终端进行编程让它针对用户希望使用的每个功能键只发送一个单独的、唯一的字符但这将限制可使用的控制字符的数目。)为了让curses能够区分“单独按下Escapce键”和“一个以Escape字符开头的键盘转义字符”它必须等待一小段时间。)curses不能处理二义性的Escape转义序列。如果你的终端上两个不同的按键会产生完全相同的转义序列就回导致curses不知该返回哪个逻辑按键。Curses对这一问题的处理方式是简单地放弃对这个转义序列的处理。彩色显示鉴于历史性原因curses只能以一种非常受限的方式来使用彩色。Curses函数库对颜色的支持有些不同:字符颜色的定义及其背景色的定义并不完全独立。必须同时定义一个字符的前景色和背景色称为颜色组合。把颜色作为字符属性使用之前必须首先调用initpair函数对装备使用的颜色组合进行初始化而对颜色属性的访问则通过COLORPAIR函数来完成。而颜色属性的激活则由wattron函数来完成它的第二个参数指定了需要设置的颜色属性。Pad在编写高级curses程序时有时需要先建立一个逻辑屏幕然后再把它的全部或者部分内容输出到物理屏幕上。Curses提供了一个特殊的数据结构pad来解决这个问题。Pad结构非常类似于WINDOW结构所有执行写窗口操作的curses函数同样可以应用于pad。但是pad有自己的创建函数newpad和刷新函数prefresh。本章最后展示了一个CD唱片应用程序的完整代码。它详细地描述了如何使用curses来编写应用程序为我们自己设计使用curses函数库带来了很大的帮助。​ 《Linux程序设计》学习笔记数据管理内存管理Linux为应用程序提供了一个简洁的视图它能反映一个巨大的可直接寻址的内存空间。此外Linux还提供了内存保护机制它避免了不同的应用程序之间的互相干扰。我们使用malloc和free函数来完成动态内存的分配和释放。与DOS下的程序不能访问超过K的内存相比在Linux系统上使用malloc可以开辟兆字节的内存空间。实际上在Linux系统上使用malloc可以申请的内存也是有限的:当申请的内存空间足够大时物理内存将耗尽此时内核将会开始使用所谓的交换空间(swapspace)当物理内存和交换空间都耗尽时或者栈超出了最大长度时内核将拒绝内存申请并可能提前终止程序。提示:Linux擅长管理内存它允许应用程序使用数量非常巨大的内存甚至使用一个单独的非常大的内存块。但是必须要记住的是:分配了两块内存并不见得肯定能够得到一个单独的可以连续寻址的内存块而很有可能是两个分开的内存块。注意:由于malloc函数返回的是一个void*指针因此我们需要通过类型转换将其转换至我们需要的指针类型。实际上malloc函数可以保证其返回的内存是地址对齐的(很有可能是字节对齐)因此它返回的指针可以转换为任何类型。空指针:当使用malloc开辟内存时往往需要测试返回值是否是空指针。这里要说的是:空指针并非等价于(void*)实际上“空指针的内部(或运行期)表达形式很可能并不是全零而且对不同的指针类型可能不一样”。因此不要想当然地把看成零值也尽量不要再使用if(!ptr)方式来测试malloc函数的返回值应该使用if(ptr==)。文件锁定Linux提供了多种特性来实现文件锁定。最简单的方法是以原子操作的方式创建锁文件所谓“原子操作”就是在创建锁文件时系统将不允许任何其他的事情发生。锁文件仅仅只是充当一个指示器的角色程序间需要通过相互协作来使用它们。为了创建一个用作锁指示器的文件使用在fcntlh文件中定义的带OCREAT和OEXCL标志的open系统调用。注意:由上述锁文件方式建立的文件严格仅属于创建它的进程也就是说即使使用open系统调用创建该锁文件的进程调用close关闭了该文件其他进程也再无法使用open系统调用打开该文件。实际上用创建锁文件的办法来控制诸如串行口之类的资源的独占式访问时一个不错的选择但它并不适用于大型的共享文件。这种情况下我们可以通过文件中的锁定区域来解决这个问题:文件的一部分被锁定但其他程序可以访问这个文件的其他部分。这杯称为文件段锁定或文件区锁定。Linux提供了至少两种方式来完成这一工作:使用fcntl系统调用和使用lockf调用。其中fcntl系统调用时最常使用的方式。而使用fcntl对文件锁定的操作在《精通UNIX下C语言编程与项目实践》的学习笔记中有详细的解释。注意:fcntl和lockf的锁定机制不能同时工作。它们使用不同的底层实现因此你决不能混合使用两种类型的调用而应该坚持使用其中的一种。提示:当对文件的区域加锁之后访问文件中的数据应该使用底层的read和write调用而不要使用高级的fread和fwrite函数这一点是非常重要的。dbm数据库所有版本的Linux以及大多数的UNIX版本都随系统带有一个基本的但却非常高效的数据存储的例程集称为dbm数据库。dbm数据库适合于存储相对比较静态的索引化数据。dbm数据库的优点是它非常容易被编译进一个可发布的二进制可执行程序因为它无需安装独立的服务器而且即使它需要的底层库文件还未被安装也不会有什么危险。dbm数据允许通过使用索引来存储可变长的数据结构然后通过索引或简单的顺序扫描数据库来检索结构。dbm数据适用于处理那些被频繁访问但却很少被更新的数据因为它创建数据项时非常慢而检索时却非常快。dbm数据有各种各样不同的版本这里重点介绍ndbm接口。dbm数据库的基本元素是需要储存的数据块以及与它关联的在检索数据时用作关键字的数据块。每个dbm数据库必须有一个针对每个要处处的数据块的唯一的关键字。为了操纵这些数据块头文件ndbmh定义了一个名为datum的新数据类型。该类型确切的定义依赖于具体实现但至少包含下面这些成员:void*dptr数据的起点sizetdsize数据的长度当我们打开一个dbm数据库时将创建两个物理文件它们的后缀分别是pag和dir而仅仅返回一个DBM类型指针它被用来访问这两个文件。这两个文件不应该被直接读写对它们的访问一定要通过dbm例程来进行。类似于FILE类型DBM类型用来访问数据库的结构。下面给出一个典型的dbm使用例程:#include<unistdh>#include<stdioh>#include<stdlibh>#include<fcntlh>#include<ndbmh>#include<asserth>#include<stringh>#defineDBFILE"tmpdbmtest"定义了dbm数据库实际存在的位置和名字#defineITEMSstructtestdata{自定义的数据块结构charmiscintvaluecharmore}intmain(){structtestdataitemsITEMSstructtestdataitemgotcharkeytouseinti,resultdatumkeydatumdatumdatadatumDBM*pDbmpDbm=dbmopen(DBFILE,ORDWR|OCREAT,)打开dbm数据库assert(pDbm!=(DBM*))memset(items,'',sizeof(items))预先安排好需要写入的数据块strncpy(itemsmisc,"First!",)itemsvalue=strncpy(itemsmore,"foo",)strncpy(itemsmisc,"Second!",)itemsvalue=strncpy(itemsmore,"bar",)for(i=i<ITEMSi){sprintf(keytouse,"ccd",itemsimisc,itemsimore,itemsivalue)自行定义关键字的模式keydatumdptr=(void*)keytousekeydatumdsize=strlen(keytouse)datadatumdptr=(void*)itemsidatadatumdsize=sizeof(structtestdata)写入数据块result=dbmstore(pDbm,keydatum,datadatum,DBMREPLACE)assert(result==)}sprintf(keytouse,"Ffd",)keydatumdptr=(void*)keytousekeydatumdsize=strlen(keytouse)datadatum=dbmfetch(pDbm,keydatum)读取数据块if(datadatumdptr){printf("Datagotn")memcpy(itemgot,datadatumdptr,datadatumdsize)printf("Getitemsdsn",itemgotmisc,itemgotvalue,itemgotmore)}elseprintf("Nodatafoundforkeysn",keytouse)dbmclose(pDbm)关闭dbm数据库exit()}上面程序中我们先存储了两个数据块继而按关键字读取一个数据块。首先我们将需要存储的数据定义为testdata结构。dbm数据库的打开和关闭使用dbmopen和dbmclose函数来完成。dbmstore函数用于向一个打开的数据库写入一个数据块第一个参

用户评价(0)

关闭

新课改视野下建构高中语文教学实验成果报告(32KB)

抱歉,积分不足下载失败,请稍后再试!

提示

试读已结束,如需要继续阅读或者下载,敬请购买!

评分:

/45

VIP

在线
客服

免费
邮箱

爱问共享资料服务号

扫描关注领取更多福利