C语言基础笔记

对学习编程者的忠告:
眼过千遍不如手过一遍!
书看千行不如手敲一行!
手敲千行不如单步一行!
单步源代码千行不如单步对应汇编一行!

 

★在1973年,美国贝尔实验室的D.M.Ritchie(丹尼斯·里奇)在B语言的基础上最终设计出了一种新的语言,他取了BCPL的第二个字母作为这种语言的名字,这就是C语言,并首次用C从新实现了UNIX操作系统。

★1978年Brian W. Kernighan和Dennis M. Ritchie出版的名著《C程序设计语言(The C ProgrammingLanguage)》,常被人们称为K&R版C教材或“白皮书”,为C语言的推广普及立下汗马功劳。在书籍方面我还要极力推荐的一本精典著作就是《C和指针》,写的特别深入、详细。

1、C语言是一门兼具高级语言功能和低级语言大部分功能的程序开发语言,所以既能开发应用软件也能开发系统软件。

2、C程序是由函数构成的,一个C源程序至少且仅包含一个main函数。一个C程序总是从main函数开始执行的,而不论main函数在整个程序的位置如何。main(int argc,char *c[])主函数也可以带参数,此时在命令行执行test.exe ma wen tao,那么总共有三个参数,argc就等于3,c[0]是源程序名"test.exe",c[1]是ma,c[2]是wen,c[3]是tao。

3、C语言是严格区分大、小写的,比如定义变量时:int a和int A是两个完全不同的变量。

4、C语言中整常数可以有3种表示形式:
      (1)默认为十进制。
      (2)以0开头为八进制,如:0123表示八进制123,十进制为83。
      (3)以0x开头为十六进制(没有直接表示二进制格式,所以可以用十六进制表示),如:0x123。

5、数据类型的长度由编译器决定,比如Turbo C给int型变量分配2个字节的存储单元,而Visual C++6.0则分配4字节。
  ★这里简单说下C语言变量在内存中的存储位置:
    (1)全局变量和静态变量都在静态内存区,说白了也就是普通内存区即进程所分配的地址空间,当程序退出自动释放。
    (2)函数的参数和局部变量都在堆栈中,函数退出后这些变量都会自动出栈,即释放内存。
    (3)用malloc、calloc、realloc分配的内存属于堆区,必须手动调用free函数释放,否则会造成内存泄露。
    (4)寄存器变量,直接存储在寄存器中,存取值速度快,只有局部自动(非静态)变量和形式参数可以这样定义。如:register ...

6、整数默认就是有符号数,浮点型常量默认为双精度,有些编译器比如Turbo C把字符型变量默认定义为signed char型,即有符号字符,这样它的范围就是-128到127之间了,但有些编译器默认字符是无符号(unsigned char)的,范围在0到255之间。

7、%模运算符(求余数)两侧均应为整型数据。

8、变量使用前必须先声明,而且声明要在代码之前(即变量声明紧随代码块左大括号)。

9、关系运算符(<,>,<=,>=,==,!=)的判断结果有真,假两个值,真值用非0表示,假用0表示,没有true和false等关键字。
   C语言的逻辑运算符(&&,||,!)与、或、非的判断结果是假则值为0,结果为真则值为非0(包括任何非0数)。

10、逻辑表达式在求解中,并不是所有的逻辑运算符都被执行,只是在必须执行下一个逻辑运算符后才能确定表达式结果时才执行后面运算,比如:a&&b&&c如果a为真(非0)才去计算b,如果a为假(0)则b和c都不会去执行,最终结果就是假。

11、if语言的判断条件是一个关系表达式或逻辑表达式,由于逻辑表达式的真用非0代表,所以if语句的判断条件可以是0和任意非0数值,包括(整数、实型、字符型、指针型),如:if(3)   printf("0k");或者if('a')printf("no");都是合法的。切记即使在条件判断和循环判断中传的是负数也会为真,即if(-2)...while(-1)等等这样的表达式都会为真,只要不是0就为真。

12、条件表达式即三目运算符x?a:b中a和b的结果类型不同,则三目运算符的最终结果会取类型较高的,比如x>y?1:1.5,如果x>y为真则最终结果为1.0。

13、for循环中for(表达式1;表达式2;表达式3),都可以省略这样就成了死循环,但分号不能去掉。而且表达式1和表达式3中都可以包括多个表达式用逗号分隔如,比如:
    for(i=1,j=2; i<100; i++,j++)。

14、while循环、do...while循环和for循环可以用break语句跳出循环,用continue语句结束本次循环,但对于goto语句和if语句构成的循环不能用break和continue进行控制。切记:
    do....while();这里while()括号后面的分号不能省略。
    break的作用只有两个:
    (1)跳出最近的那层循环。
    (2)跳出最近的整个switch语句。

15、提到字符串就必须说说字符串常量,比如:
    char *c = "asdf";这样用指针定义的字符串常量不能改变字符串的任何字符,因为字符串常量存储在静态内存区的只读区域,所以如果你试图用*c++ = 'm'等语句想改变字符串的内容,系统编译不会发现问题,但运行时则会报错,这个问题前期一般很难发现,所以必须谨慎,字符数组可以修改如:char a[]="sdf",(奇怪的是好多C程序书籍居然没有提到这一点,真不明白现在的作者都是以何目的而写作的)。还有一点就是字符串连接:"ma" "wentao"就等价于"mawentao"

16、关于数组要说的首先是数组名,数组名在作为&和sizeof的操作数时,不会当做指针用,此时就代表整个数组,即char a[] = "bao"; &a就是数组的起始地址,即&a[0].
    其次,函数不能返回数组,只能返回数组的首地址(指针),关于数组的初始化必须按顺序来。
    最后,数组中的元素长度都是相同的,即都是相同的类型,所以数组元素不可能是像函数等等这些没固定长度的元素,而且定义数组时下标必须是个常量即数组大小在编译时就要确定。
    (ANSI标准规定数组下标访问不能超过数组左边界,但VC++6.0没这限制,不论数组左边界还是右边界都可以随便超出访问,不过没实际意义,所以程序员自己要注意)

17、关于指针,好多作者都会给读者一个下马威,说是指针特别复杂,其实真没有什么,指针就是地址。一般指针变量自身都占4个字节,它所指向的类型除了基本类型外比较复杂的有结构体 、数组、函数。数组名作为参数传递时就是首地址的拷贝,而结构仍是结构本身的拷贝。
    指针复杂声明:
    int **p; 指针的指针。
    float(*p)[4];数组指针。
    int (*p)(int); 函数指针。
    array[5]等价于5[array],因为内部都是指针加下标偏移量实现的。
    (复杂声明的理解技巧其实很简单,就是每次都从变量名开始根据它前后的符号确定其根本)

18、extern声明外部变量,可以扩展外部变量的作用域。如:extern a,b;...int a=10,b=11;虽然a和b在extern语句之后定义但在extern后面且在a和b定义之前的语句都可以用a和b。 extern用于函数定义表示全局可见(不加也可以),用于变量说明它在其他地方定义。

19、在编译时,对类型是不分配空间的,只对变量分配空间。比如:建立结构体类型struct student{char name[],int age};此时不会分配内存,只有定义了变量在系统编译时才分配空间。比如:struct student student1;

20、sizeof操作符可以获得一个类型所占的字节数,sizeof不是个函数,如果后面的操作数不是个类型名就不用加括号。如:int i; sizeof i;当sizeof操作结构时,好多时候都大于结构中所有成员的长度和,这是由于字节对齐所造成的。操作数组时会返回整个数组的长度。

21、typedef声明新的类型名来代替已有的类型名。比如:typedef int INTEGER
    ★typedef和define的主要区别两个:
        (1)可以用其他类型说明符对宏类型名进行扩展,但对typedef所定义的类型名却不能这么做。
        (2)在连续的几个变量声明中,用typedef定义的类型能够保证声明中的所有变量均为同一种类型,而用define定义的类型则无法保证。比如连续定义指针类型时,define就会出问题。

22、>>右移操作符:Turbo C和其他一些编译器采用的是算数右移,即对有符号数右移时,如果原来符号位原来为1,左面移入高位的是1。如果某个编译器对于右移采用逻辑右移则永远在高位补0,所以右移负数时存在不可移植性问题。

23、位段:C语言中允许在一个结构体中以位为单位制定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域”
      利用位段能够用较少的位数存储数据。(前提也是在结构体内,并且类型只能是int、unsigned int、singned int)
      例如:struct data
        {
           unsigned a:8;
           unsigned b:2;
           unsigned c:6;
        }da;这样定义的结构体a、b、c分别占8位、2位、6位。

24、所有非整形函数一定要在调用之前声明。(没有被声明就调用的函数被默认为返回整型)
      (1)函数前面不写返回值默认就返回整型,但函数体此时可以不返回任何东西,也可以返回整数。
      (2)函数不返回任何东西,可以显示的用void声明,比如这样定义主函数void main(){}。   
      (3)函数如果没有参数,可以用void显示说明,即void remove(void){}

25、字符常数是int型,因此sizeof('a')是sizeof(int)即值为4,而不是1。

26、关键字const并不能把变量变成常量,在一个符号前加上const限定符只是表示这个符号不能被赋值,可以初始化一次。
    注意:const int * p;和int const * p;都叫常量指针(这种读法很简单,因为先是const再是*),都是指针所指的对象不能修改。
      int * const p;这和字符串常量、数组名(当做指针时)一样都叫指针常量,表示指针本身是不可变的。
          const int * const p;和 int const * const p;表示指针和所指对象都是只读的。

27、void *v类型参数是一个通用的指针类型,即这种类型的指针可以转换成任意具体类型的指针,但它不可以移动,比如:v++,v-1等等,这是因为此指针所指向的对象大小无法确定,那么它自然就不知道+1是移动多少了。(这个解释实在太经典了,嘿嘿)

28、C语言在定义数组时,需要指定数组中元素的个数,方括号中的表达式用来表示元素的个数,即数组长度。下标只能是表达式常量和符号常量,不能包含变量,也就是说C语言不允许对数组大小作动态定义,即数组的大小不依赖于程序运行过程中的变量值。
    例如:int a=10; int s[a];这样是错误的。
    ●C语言在编译时就要确定任何变量的存储空间大小,包括数组。(可以想想汇编数据段中的内存分配)

29、"0123456789"[n%10]这个表达式将返回n(整数)的最后一位数值,别忘了字符串常量就是一个指针常量。(为了防止字符表中的数字没有顺序排列)

30、对于参数的计算顺序和整形数右移是否扩展符号位,标准都没有规定,只能依赖编译器了。

31、C语言中两个字符串常量会自动合并。比如char a[]="ma""wentao"; 等价于char a[]="mawentao";

32、有好多人经常问标准库函数到底是在哪里实现的?为什么我们很简单的用include<math.h>包含后,可就是找不到math的实现,感觉很奇怪,之前我也觉得很好奇,其实等你了解了动态链接(.dll)、引用链接库(.lib)一切都会明白了,在编译器的某个文件夹下存在好多.dll和.lib文件,其中dll文件就是动态链接库,内部就是函数的实现,lib库中有那些函数名的声明引用,当系统发现你调用pow()函数时,就会去相应文件夹下lib文件中找到pow在哪个动态库中调用了,然后把动态库中的此函数体自动加载到内存执行,大概过程就是这样。

33、C语言把文件看做一个字符(字节)序列,即C文件是一个字节流或二进制流,根据数据的组织形式不同,可以分为ascii文件和二进制文件ascii文件也叫文本文件,它的每一个字节存放一个 ascii代码,代表一个字符。二进制文件是把内存中的数据按其在内存中的存储形式原样输出到文件磁盘上存放。 注意文本/二进制文件区别只是发生在文件打开时,一旦文件打开之后,在其上调用什么I/O函数无关紧要。
    用fopen打开文件时,路径有两种写法:双反斜杠//、单正斜杠/。

34、有些专家建议在C语言的操作符中牢记两个优先级就够了:乘法和除法优先于加法和减法,在涉及到其他操作符时,一律加上括号。虽然有点夸张了,但我觉得我们真不应该把时间浪费在死记操作符的优先级上,而且为了代码的可读性和维护性就应该多加括号。

35、i=*x/*y;这里本意是指针x所指向的内容除以指针y所指向的内容然后赋给i,但/*被理解为注释开始了,编译器会报错,所以/和*之间必须加上空格。这也是空格作用体现的一个很好例子,因此我们在写代码时一定要养成一个好的习惯,操作符两边都以空格分隔。

37、函数内部不能返回一个局部数组指针,如int* f(){int a[3]={1,2,4}; return a;},这样返回了一个指针a,但函数结束后整个数组都被释放,a指向的内存内容是个未知的,或许已经被别的内容覆盖,这是很危险的。

38、声明只确定了变量的基本类型以及初始值(如果有的话).

39、如何解析C语言声明?
    1、取最左边的标示符(变量名)
    2、查看标示符右边的下一个符号,如果是方括号对于每一对方括号表示“...的数组”;如果是一个左括号,到右括号为止的内容表示“返回...的函数”;(如果变量名临近右边无任 何有价值的符号就直接走第3步,比如int (*p)[2])
    3、接着查看的左边符号,如果是一个左括号,这个左括号把已经处理的部分声明组合在一起,直到遇见对应的右括号,然后从第2步从新开始;
    4、上面三部分析完后,在看已经处理的部分左边的符号如果是下述之一:const、volatile、*,则继续向左边读符号,直到所读符号不再是上面那三个之一。如果符号是const,表示“只读”;如果是volatile表示"volatile";如果是*,表示“指向...的指针”然后重复第三步;
    5、剩下的符号形成声明的基本类型,剩下的所有符号可以一并阅读,如:static unsigned int。
   
    切记:const或volatile关键字后面如果紧跟类型说明符如(int、long),则它作用于类型说明符,其他情况下都作用于左边的临近指针星号。

举例比如:char * const *(*next)();我们来简单分析下这个声明,就按照上面的5个步骤进行:
    1、取最左边的标示符:next,表示变量next是个...;
    2和3都不匹配;
    4、next左边是*,表示“next是指向...的指针”;再重复第三步,匹配到了一对小括号后再从第二步开始,表示“返回...的函数”;接着又到第三步:不匹配,再去第四步:左边遇到一个*即(*next)外面紧跟const的那个*,表示“指向...的指针”,因为右边我们已经分析完了就不再看前三步了,继续第四步遇到了const,“表示只读”;再又是一个“指向...的指针”,最后就是char类型定义了。
    ●上面所有合起来就是next是一个指向函数的指针,该函数返回另一个指针,该指针指向一个只读的指向char的指针。
再举例:
    char *(* c[10])(int **p)就表示:c是一个指针数组,即里面的元素全是指针,每个指针都指向一个函数,即函数指针,该函数有个指向int型指针的指针的参数,返回值是个char型指针。
    ●注意:在数组中被函数指针指向的所有函数都把一个指向指针的指针作为它们的唯一参数。(上面就是一个例子)

40、关于补码请记住:对于有符号数,正数和负数都是由补码构成的。(汇编语言)
    一个正数的补码取反加1后,为其负数的补码;负数的补码取反加1后,为其绝对值。

41、D:/soft/vc++6.0/VC6CN/VC98/INCLUDE,这是VC++6.0中头文件所存放的目录。(我的VC装在D盘的soft下)
    用<>括起来的头文件一般先会在标准位置搜索即上面的目录,用“”括起来的头文件首先会在当前目录搜索,然后去标准位置搜索。

42、switch(此处必须是个整型){
    case 1: ......; break;
    case 2: ......; break;
    default: .....; break; 此处break可加,也可不加,不过加了以后维护方便些。
    }

43、判断偶数的小技巧就是看二进制最后一位是否为0,如果是0则为偶数,为1就是奇数。

44、可变参数列表的应用主靠头文件<stdarg.h>,里面有个va_list结构体和三个宏va_start、va_arg、va_end。(具体应用任何C书籍都有提到)
   
45、struct student1{
        char a;
        int b;
        char c;   
    } s1;
  由于字节对齐要求声明上面结构体比较浪费内存(比如在32位机器上,s1会浪费6个字节,因为CPU从内存获取数据时是从地址空间为4的整数倍开始取的,则CPU下个周期会移动4byte取数据)
  所以可以这样声明:
    struct student2{
        int b;
        char a;       
        char c;   
    } s2;
  而且这里结构体变量名s2和数组名不同,s2不会当做一个指向结构体第一个成员的指针,s2就代表整个结构。

46、函数指针用法,int f(float); int (*p)(float) = &f; 这里的&可加可不加(p = f),因为函数名在使用时本来就当做函数指针用的,接着函数可以这么调用:
        int i;   i = f(7.28);  i = (*p)(7.28);  i = p(7.28);  
       
47、#define 宏定义中:#argument这种结构,可以把宏参数argument转换成字符串。如果宏内容中出现两个#号,比如:sum ## i; ##会把位于它两本的符号组合成一个标识符。切记:宏语句最好不要以分号结束。(当宏内容太长了,可以换行并在除过最后一行的所有行末尾加上反斜杠)。 #undef用于移除一个宏定义,如果一个现存的名字需要被重新定义,那它的旧定义必须用#undef移除。
   

48、条件编译指令:
     (1)#if  constant-expression    //这里#if后面的条件是个常量表达式,即字面值常量或者由#define定义的符号。当其值为0则statements不会被编译,为非0时会正常编译。
            statements
    #elif constant-expression    //这里elif出现的次数不限,
            other statements
    #else
            other statements
    #endif

     (2)#ifdef symbol    //如果定义了symbol,此条件编译指令和#if defined(symbol)是等价的,这里的defined也是个预处理指令。
    #ifndef symbol  //如果没有定义symbol,此条件编译指令和#if !defined(symbol)是一样的。
   
     (3)#error        //允许你生成错误指令

     (4)#line        //通知预处理器输入的行号

     (5)#progma        //支持因编译器不同的特性

49、为了防止#include指令包含多个相同的头文件,可以使用:
                        #ifndef HONG  
                        #define HONG 1
                            ......
                        #endif
 
50、流:就C而言,所有的I/O操作只是简单的从程序移进或移出字节序列,这些字节序列就是字节流简称为流。我们的程序只关心创建正确的输出字节数据,以及正确的解释从输入读取的字节数据。特定I/O设备的I/O操作细节对程序员来说是隐藏的。
    绝大多数流是完全缓冲的,这意味着“读取”和“写入”实际上是从一块被称为缓冲区的内存区域来回复制数据。用于输出流的缓冲只有当它写满时才会被刷新(flush)到设备或文件中。同样,输入缓冲区当它为空时通过从设备或文件读取下一块较大的输入,重新填充缓冲区。
    ●流分为:文本流(文本文件、字符流)和二进制流(二进制文件、字节流),这两种流的划分都是由写入和读取时的打开方式决定的。

51、文本文件(文本流):是以特定的编码方式写入I/O设备,并且要以特定的解码方式打开,否则这些信息将会变成乱码而无法解释。

52、二进制文件(二进制流):二进制流中的字节将完全根据程序编写他们的形式写入到文件或设备中,而且完全根据他们从文件或设备读取的形式读入到程序中。

53、FILE是个结构,在<stdio.h>中定义,用于访问一个流。如果你同时激活几个流,每个流都有一个相应的FILE与它关联。

54、对于每个ANSI C程序,运行时系统必须至少提供三个流,标准输入(stdin)、标准输出(stdout)、标准错误(stderr),他们都是一个指向FILE结构的指针。操作系统一般都允许程序员修改缺省的输入输出设备。比如修,(键盘、屏幕)。
    ●I/O函数以三种基本形式处理数据:单个字符(getchar和putchar家族)、文本行(gets和puts家族、scanf和printf家族)和二进制数据(fread和fwrite家族)。对于每种形式,都有一组特定的函数对他们进行处理。

55、打开流的常用模式:
                读取        写入        可读可写(用于更新)       可读可写(有就删除)    添加(有不会删除)        更新(可读可写)
   文本:     "r"        "w"          "r+"                             "w+"                           "a"                     "a+"
二进制:   "rb"        "wb"        "rb+"                           "wb+"                         "ab"                   "ab+"
    ●一个简单的规律是文本文件后边加个b就成了二进制文件模式,比如r+到二进制模式的转换就是:rb+或r+b。


网络编程知识
★套接字:为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了称为套接字(Socket)的接口。
  区分不同应用程序进程间的网络通信和连接,主要有3个参数:通信的目的IP地址、使用的传输层协议(TCP或UDP)和使用的端口号。Socket原意是 “插座”。通过将这3个参数结合起来, 

与一个“插座”Socket绑定,应用层就可以和传输层通过套接字接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。
一、套接字就是网络编程接口中基于TCP面向连接的socket编程服务器端流程如下:
(1)创建套接字,包括初始化(socket)。
(2)将套接字绑定到一个本地地址和端口上(bind)。
(3)将套接字设为监听状态,准备接收客户请求(listen)。
(4)等待客户请求到来,当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept)。
(5)用返回的套接字和客户端进行通信,获得数据用recv()方法,发送数据用send()方法。
(6)返回等待另一客户请求。
(7)关闭套接字。

二、基于TCP面向连接的socket编程客户端流程如下:
(1)创建套接字包括初始化(socket)。
(2)向服务器发出连接请求(connect)。
(3)和服务器端进行通信,发送数据用send(),接收数据用recv()。
(4)关闭套接字。

●在客户端不用调用bind绑定函数,因为服务器需要接收客户端请求,所以必须告诉本地主机它打算在哪个IP地址和哪个端口上等待客户请求。因此服务器端必须调用bind函数实现这一功能。
而对客户端来说,当它发起连接请求,服务器端接收该请求后,在服务器端就保存了客户端的IP地址和端口信息。因此服务器端就可以调用recv/send函数与客户端进行通信。



★C语言的数据类型包括: 基本类型、构造类型、指针类型、空类型。
    基本类型: (1)int整形(Turbo C占2个byte,Visual C++ 6.0占4个byte),包括short、int、long前面加unsigned是无符号.

          (2)char字符型(1 byte),内部都存的是ASCII值,可以与整形互换。

          (3)float浮点型(Visual C++ 6.0占4个byte,和int一样),float是单精度型、double是双精度型、longdouble长双精度,系统默认把小数当做双精度对待,后面加个f或F就成单精度了。浮点数按照指数形式存储,大部分C编译器以24位表示小数部分(包括符号),以8位表示指数部分(包括指数符号),小数部分占的位(bit)数愈多,数的有效数字愈多,精度也就愈高。指数部分占的位数愈多,则能表示的数值范围愈大。

          (4)enum枚举类型:如果一个变量只有几种可能的值,则可定义为枚举类型。枚举类型就是将变量的值一一列出来,变量的值只限于列举出来的值的范围内。C编译对枚举元素按常量处理,故称枚举常量。但它们是有值的,即从第一个元素开始由0、1、2等等一直到最后一个元素。当枚举元素在做算术运算时就会用这些值去运算。
         用法举例:enum bool{true,false}; enum bool e=true; //e就是一个枚举类型变量,其值为true.
 
   构造类型: (1)数组类型:必须存储相同的数据类型。字符串也可以这样定义char c[]="abc";   
       
         (2)struct结构体类型:把不同类型的数据组合成一个有机的整体,以便于引用。这时会用到的数据结构就是结构体。结构体变量所占的内存长度是各成员占的内存长度之和,每个成员分别占有其自己的内存单元。结构体的成员也可以是结构体变量,结构体指针变量(*p).age就等于p->age。
   
         (3)union共用体类型:把不同类型的变量存放到同一段内存中,共用体变量所占的内存长度等于最长的成员长度。联合指针不能作为函数参数,函数也不能返回联合的指针。
         不能直接引用共用体变量,只能引用其中的成员。比如:union data{int i;char c}a,b;不能直接用a或b,只能用a.i或a.c。这种定义“union data{int i;char c}a,b;”等价于union data{int i;char c};先声明,然后定义变量:union data a,b;这些都跟结构体类似。
  
   指针类型: 指针就是内存地址,指针的指针就是指向地址的指针。 指针也可以作为函数的返回值,如:char *new() 
  
   空类型: void类型。

★位运算符
    &   按位与
    |   按位或
    ^   按位异或(判断两个相应的位值是否为异,即是否不同,如果不同则返回真(1),如果相同结果为0)
    ~   取反
    <<  左移
    >>  右移

★逻辑运算符
    &&  与
    ||  或
    !  非
         
★键盘特殊按键的ASCII十进制数:
    空格:32
    tab:9
    回车:10
    数字0:48(十六进制是30)
    A:65
    a:97

■C标准函数库中的常用函数:
    c的库函数程序都是c的运行环境提供的,即C编译器所提供。而这些库函数我们是看不到源码,这些库函数的功能是通过编译器提供的静态链接库和动态链接库所实现的,在windows中静态链接库后缀是.lib,动态库是.dll,这些链接库在安装编译器时自动安装到了某个文件下。(动态链接库扩展性好,所占资源空间小)
       
    ★<stdio.h>输入输出函数头文件
        1、FILE *fopen(char const *name, char const *mode)返回一个指向FILE结构的指针,失败返回NULL,error会提示问题原因。比如:FILE fp; fp=fopen("test.c", "r");
           FILE *freopen(char const *filename, char const *mode, FILE *stream)重新打开一个流,第一个参数是指定的文件,第二个是模式,第三个是之前从fopen函数返回的流,也可以是标准流stdin、stdout、stderr。
        2、int fclose(FILE *f),当顺利的执行了关闭返回0,否则返回EOF(-1)。关闭一个流可以防止与它相关联的文件被再次访问,并保证任何存储于缓冲区的数据被正确的写到文件中(即对于输出流,会刷新缓冲区),并且释放FILE结构所占用的内存。
        3、字符I/O操作
            ●字符输入家族(此家族函数从流中读取下一个字符,并把它作为函数的返回值返回,如果流中不存在更多的字符,函数就返回EOF):
                (1)int getchar(void)从标准输入读取字符,返回用户输入的第一个字符的ASCII码,用户输入的所有字符都保存在键盘缓冲区中,直到按回车后,getchar才开始从stdin流中每次读入一个字符,回车字符也会被getchar接收。
                (2)int fgetc(FILE *stream);
                (3)int getc(FILE *stream);和fgetc等效,由fgetc通过宏实现。
               ●字符输出家族(如果函数写入到一个已经关闭的流,将导致函数失败,则会返回EOF)
                (1)int putchar(int character);第一个参数是被打印的字符,函数把这个整型参数裁剪为一个无符号字符型值.比如:putchar('asdf');只打印一个字符.
                (2)int fputc(int character, FILE *stream);
                (3)int putc(int character, FILE *stream);和fputc等效,由fputc通过宏实现。
            ◆切记:上面两个家族里,只有fgetc和fputc是真正的函数,其余都是通过#define指令定义的宏,还有就是getchar和putchar只能访问标准IO设备。

        4、int ungetc(int character, FILE *stream);把一个先前读入的字符返回到流中,此字符将是下一个输入操作所返回的第一个字符。

        5、文本行I/O操作
            ●未格式化的行I/O
                (1)char *fgets(char *buffer, int buffer_size, FILE *stream);从stream流中读入buffer_size个字符存到str里,最后给str末尾加个NUL使其成为一个 字符串,函数返回第一个参数。当读取一个换行符并存储到缓冲区后就不再读取,而且这个结尾换行符会被存储到缓冲区。在任何字符读取前就到达了文件末尾,缓冲区就未进行修改,此时返回一个NULL指针。

                (2)char *gets(char *buffer)功能基本同上,区别在于此函数从标准输入接收文本行而且不会在缓冲区存储结尾的换行符。gets函数的结束标记就是键入回车,此时返回NULL。 书上说此函数为玩具函数,所以尽量少用之,因为它很容易造成溢出。

                (3)int fputs(char const *buffer, FILE *stream);这两个函数就不用解释了,buffer是个字符串,参数用string代替更合适。
                (4)int puts(char const *buffer);puts在写入之后会自动加上一个换行符,而fputs则不会。
   
            ●格式化的行I/O(这里pintf和scanf可以处理多行文本)
                scanf家族:每个原型的省略号表示一个可变长度的指针列表,从输入转换而来的值逐个存储到这些指针参数所指向的内存位置,返回实际转换的数目。       
                    (1)int fscanf(FILE *stream, char const *format, ...);从stream中读取。当对123abc进行%d转换时就把123转换后存储到内存中。
                    (2)int scanf(char const *format, ...);从标准输入读取。
                    (3)int sscanf(char const *string, char const *format, ...);从第一个参数给出的字符串中读取字符。
               
                printf家族(返回值为实际存储的字符数):
                    (1)int fprintf(FILE *stream, char const *format, ...);向stream中格式化输出。
                    (2)int printf(char const *format, ...);标准输出函数.
                格式化有:
                %d(整形输出,还有%md这样会打印m个宽度,也会让输出结果右对齐。如果%md中m为负数则多余的空格输出在数字的右边,比如%-4d在右边输出4个空格)。
                %l(长整型).
                %c(字符型输出)。
                %f(双精度浮点输出,printf("%.2f")打印小数点后两位,.0则不打印小数)。
                %u(无符号十进制数输出)。
                %x(十六进制输出)。
                %o(八进制输出)。
                %s(输出一个字符串)。
                %e(指数形式输出实数)。
                %g(根据数值大小自动选f或者e格式选择宽度较小的,且不输出无意义的0)。
                %%表示打印一个%号而不是用/%。
                注意:除了X,E,G大小写都可以外,其他格式字符必须用小写,比如%d就不能写成%D。
          
                printf个性示例:
                   printf("%f/n",5/3);结果相当另类,3/2是0,5/3也等于0。原因在于两个整型数据在做运算时,结果必然是整型,而5/3或者3/2的整型结果都等于1,它们 所占的内存空间只有4个字节(编译器不同,int分配的内存空间也不同),但你用float直接输出,此时不会强转成浮点型,而浮点数在内存中的存储形式是以指数存储,这样高字节即指数前面的字节存的都是0,所以结果就是0了。       

                    (3)int sprintf(char *buffer, char const *format, ...);把数据作为一个以NUL结尾的字符串存储到指定的buffer中。返回值为字符串长度,buffer容易溢出,造成和它相邻的内存被修改,所以程序员自己要控制。       
                   

        6、二进制I/O操作
            (1)size_t fread(void *buffer, size_t size, size_t count, FILE *stream);
            (2)size_t fwrite(void *buffer, size_t size, size_t count, FILE *stream);
                buffer是保存数据的内存位置指针,size是缓冲区每个元素的字节数,count是读取或写入的元素数,stream是流
                buffer被解释为数组,count参数指定数组中有多少个值,所以读取或写入一个标量时,count值为1.
                返回值:两个函数的返回值是读取或写入的元素数目,并非字节数。
       
        7、int fflush(FILE *stream)刷新缓冲区,如:fflush(stdin);刷新标准输入缓冲区。                   

        8、缓冲区指针定位函数:
            (1)long ftell(FILE *stream);返回流当前位置,即下一个要读取或写入的位置离文件起始位置的偏移量。
            (2)int fseek(FILE *stream, long offset, int from)改变文件的位置指针。offset是偏移量(字节数),from有三个值,分别是SEEK_SET(从流的起始位置开始)、SEEK_CUR(从当前位置起)、SEEK_END(从文件尾部的后面开始),ftell的返回值总是可以用于fseek中。
            (3)void rewind(FILE *stream);使位置指针指向文件的开头,并使feof函数的值恢复为0。
            (4)int fgetpos(FILE *stream, fpos_t *position);           
            (5)int fsetpos(FILE *stream, fpos_t const *position);这两个不常用。

        9、流错误函数:
            (1)int feof(FILE *stream);用来测试stream所指向的文件当前状态是否为“文件结束”,如果文件结束返回真,否则返回0.此状态可以通过对流执行fseek、rewind、fsetpos函数来清除。
            (2)int ferror(FILE *stream);报告流的错误状态,如果出现任何读写错误就返回真。
            (3)void clearerr(FILE *stream);对指定流的错误标志进行重置,使文件错误标志和文件结束标志置为0.
       
        10、改变流缓冲区函数:(这两个函数只有当指定的流被打开但还没有在它上面执行任何其他操作前才能被调用)
            (1)void setbuf(FILE *stream, char *buf);设置了数组buf(大小必须为BUFSIZ,定义在<stdio.h>),用于对流进行缓冲。如果buf为NULL,setbuf将关闭流的所有缓冲。
            (2)int setvbuf(FILE *stream, char *buf, int mode, size_t size);此函数功能更强大,mode指定缓冲的类型,_IOFBF为完全缓冲的流,_IONBF不缓冲的流,_IOLBF指定一个行缓冲流,即每当一个换行符写入到缓冲区时,就进行刷新缓冲。buf为NULL,那么size就必须为0.

        11、创建临时文件函数:
            (1)FILE *tmpfile(void);创建一个文件,并以wb+模式打开,当文件被关闭或程序终止时,这个文件便自动被删除。
            (2)char *tmpnam(char *name);创建临时文件的名字,此名字不会与已经存在的文件名同名,只要调用次数不超过TMP_MAX次,如果name参数为NULL,函数返回一个指向静态数组的指针,该数组包含了被创建的文件名,如果name存在,则返回参数,并在name数组中创建文件名。
       
        12、文件操作函数:(不执行任何输入/输出操作),如果执行成功,函数就返回0,失败返回非0.
            (1)int remove(char const *filename);删除一个指定的文件,如果当remove被调用时文件处于打开状态,其结果取决于编译器。
            (2)int rename(char const *oldname, char const *newname);改变一个文件的名字,从oldname改为newname,如果newname已存在结果由编译器决定。
       
        13、EOF(end of file)文件结束标志也定义在这个头文件中,是一个整形数,经过打印EOF值为-1。EOF在键盘上的快捷键是ctrl+D或者ctrl+Z,是二者之一的一个,不加这个判断也是可以的,只不过显得不够严谨。
       
        14、void perror(char const *message);以一种简单、统一的方式报告错误。如果message不是NULL并且指向一个非空字符串,perror函数就打印出这个字符串,后面紧跟一个分号和一个空格,然后打印出一条用于解释errno当前错误代码的信息。●errno在errno.h中定义。只有当一个库函数失败时,errno才会被设置。
            举例:
                FILE *fp = fopen("test", "r");
                if(!fp) {
                    perror("test");
                    exit(EXIT_FAILURE);
                }
                如果fp是NULL的话,则perror会打印类似这样的句子:test: No such file or directory
       
        15、FOPEN_MAX和EOF一样,都是标准I/O常量,它确定了一个程序最多能打开几个文件。
       
        16、FILENAME_MAX是个整型值,提示最长的文件名长度。   
       

    ★<string.h>字符串/字符操作函数头文件
        ●内存操作:
        1、void *memcpy(void *dst, void const *src, size_t length); 从src起始位置开始复制length个字节到dst的起始位置,不能出现内存重叠,否则结果是未定义的。

        2、void *memmove(void *dst, void const *src, size_t length); 和上面差不多,只是memmove允许原内存和目标内存重叠。

        3、void *memcmp(void const *a, void const *b, size_t length); 按照无符号类型对a和b前length个字节进行比较。

        4、void *memchr(void const *a, int ch, size_t length); 从内存a开始查找第一次ch出现的位置,并返回此位置指针,共查找length个字节。

        5、void *memset(void *a, int ch, size_t length); 把从a开始的length个字节都设为字符ch。

        ●字符串操作:
        1、char *strcpy(char *dst,char const *src),字符串复制函数,把字符串src复制到dst中,返回dst的一份拷贝。   
       
        2、char *strcat(char *dst,char const *src),字符串连接函数即把src连接到dst后面,返回dst的一份拷贝。       
       
        3、int strcmp(char const *s1,char const *s2),字符串比较函数,s1>s2则返回正整数,相等返回0,s1<s2则函数值等于负整数。
       
        ●长度受限制的函数:
        1_1、char *strncpy(char *dst, char const *src, size_t len),将src中前len个字符复制到dst中。
       
        1_2、char *strncat(char *dst, char const *src, size_t len), 只连接len个字符

        1_3、int strncmp(char const *s1, char const *s2, size_t len), 只比较两个字符串前len个字符。   
       
        ●查找一个字符
        1、char *strchr(char const *str, int ch); 查找str中ch第一次出现的位置,并返回此位置指针。
       
        2、char *strrchr(char const *str, int ch); 返回最后一次出现的位置。

        ●查找任何字符
        1、char *strpbrk(char const *str, char const *group); 返回str中第一个匹配group中任何一个字符的位置。

        ●查找一个字串
        1、char *strstr(char const *s1, char const *s2); 在s1中查找整个s2第一次出现的位置,并返回该位置指针。

        ●查找一个字符串前缀
        1、size_t strspn(char const *str, char const *group); 返回str起始部分匹配group中任意字符的字符数。(可能有点绕,试试就很清楚了)
        2、size_t strcspn(char cosnt *str, char const *group); 与上面函数正好相反,它返回str起始部分不与group中任意字符匹配的个数。

        ●查找标记,就是返回由某个分隔符隔开的字符串字串。
        1、char *strtok(char *str, char const *sep); 此函数比较复杂,简单理解就是函数第一次返回str字符串中由sep中任何一个字符分割开的字串,第二次把str参数设为NULL,可以返回接下来的分割字串,可以循环调用,直到分割结束。需要注意的是此函数内部会对str分割后的串做记录,所以第一次之后调用都必须把函数strtok的第一个参数设为NULL,否则它永远就只返回str第一次分割的字串,而不会向后进行分割。

        5、size_t strlen(char const *string),求字符串中的实际长度。
       
        6、strlwr(字符串),大写转换成小写。
       
        7、strupr(字符串),小写转大写。
       
        8、char *strstr(s,t),返回一个指针,它指向t第一次出现在s中的位置,如果s不包含t则返回NULL。

    ★<math.h>数学函数头文件
        三角函数:
            (1)double sin( double angle );正弦,参数是一个用弧度表示的角度。
            (2)double cos( double angle );余弦,同上。
            (3)double tan( double angle );正切,同上。
            (4)double asin( double value );反正弦。
            (5)double acos( double value );反余弦
            (6)double atan( double value );反正切
            (7)double atan2( double x, double y );y/x的反正切。
       
        双曲函数:
            (1)double sinh( double angle );双曲正弦。
            (2)double cosh( double angle );双曲余弦。
            (3)double tanh( double angle );双曲正切。

        对数和整数函数:
            (1)double exp( double x );返回e值的x次幂,也就是e的x次方。
            (2)double log( double x );返回x以e为底的对数。
            (3)double log10( double x );返回x以10为底的对数。

        幂:
            (1)double pow( double x, double y );返回x的y次方。
            (2)double sprt( double x ); 返回参数x的平方根。
        其他:
            (1)double fabs( double x )求x的绝对值。
            (2)double floor( double x );返回不大于其参数的最大整数值。       
            (3)double ceil( double x );返回不小于其参数的最小整数值。           
            (4)double fmod(double x,double y);返回x除以y的余数。

    ★<stdlib.h>使用函数头文件
        ●整形函数:
            (1)int abs( int value ); 返回整形参数的绝对值。   
            (2)long int labs( long int value ); 返回长整形的绝对值。
            (3)div_t div( int numerator, int denominator ); numerator分子除以分母denominator,返回div_t类型的结构,此结构为{ quot(商), rem(余数)}
            (4)ldiv_t ldiv( long int numer, long int denom ); 功能同上,只是参数对象是long型,返回值是一个ldiv_t结构。           

        ●随机数:
            (1)void srand( unsigned int seed ); 对随机数产生器初始化,一般传系统时间作为种子。如:srand( time( 0 ) );
            (2)int rand( void ); 返回一个范围在0和RAND_MAX(至少为32,767)之间的伪随机数,当它重复调用时,返回这个范围类的其他值。通常用取模获得更小的范围。

        ●字符串转换:
            (1)int atoi( const char *string ); 把字符串s转换成int类型。
               char *itoa(int value, char *string, int radix); 把一整数转换为字符串(非标准库函数),不过大部分编译器都已经实现了,radix是进制(2、10、16)。
            (2)long int atol( const char *string); 把字符串转换成长整型。
               char *ltoa( long value, char *string, int radix ); 长整型转换成字符串。(非标准库函数)
            (3)double atof( char const *string );把字符串转换成一个double值,忽略任何缀尾的非法字符。
            (4)long int strtol( char const *string, char **unused, int base ); 跟atol一样也是把字符串转换成长整形,只是它用unused存储一个指向转换值后面的第一个字符的指针.第三个参数是基数,如果base不是0,则基数为2到36之间的任意进制.       
            (4)double strtod( char const *string, char **unused );基本同上。
            (5)unsigned long int strtoul( char const *string, char **unused, int base ); 功能同3,只是转换成无符号长整形。

        ●动态内存分配:
            (1)void *malloc(size_t size)动态分配一个长度为size的连续空间。返回一个指向分配域起始地址的指针,类型是void,必要时可以转换此指针的类型。

            (2)void *calloc(size_t n,size_t size)分配n个大小为size字节的连续空间。比如可以给一维数组动态分配空间,n为数组元素个数,没个元素长度为size。
       
            (3)void *realloc(void *ptr, size_t new_size) 改变一块已经动态分配的内存大小,ptr要么是已经动态分配的内存,要么是NULL,其余值都会报错。如果第一个参数是NULL此方法就和malloc一样。

            (4)void free(void *p)释放p指向的内存区,使这部分内存区能被其他变量使用,其中p是此前通过调用malloc或calloc函数得到的指针。
                下面是一个经典的错误程序(已经释放的存储空间不能使用):
                for(p=head; p!=NULL; p=p->next) free(p);    //p已经释放了就不能在使用p=p->next。
       
        ●排序和查找
            (1)void qsort(
                    void *base, size_t n_elements, size_t el_size,
                    int (*compare)(void const *, void const *)
                     );
                在一个数组中以升序方式对数据排序,第一个参数是要排序的数组;
                                第二个参数是数组元素个数;
                                第三个参数是每个元素的长度;
                                第四个参数是一个指向比较大小的函数指针。

            (2)void *bsearch(
                    void const *key, void const *base, size_t n_elements,
                    size_t el_size, int (*compare)(void const *, void const *)
                    );
                在一个已经排好序的数组中用二分查找一个特定的元素。如果找不到,就返回一个NULL。
                                第一个参数指向你需要查找的值;
                                第二个参数指向查找所在的数组;
                                第三个参数指定数组元素的个数;
                                第四个参数是每个元素的长度;
                                第五个参数是比较大小的函数指针;

        ●终止执行
            (1)void exit(int status);用于终止一个程序的执行,status参数返回给操作系统,用于提示程序是否正常完成,这个值和main函数返回的整型状态值相同。预定义符号中有EXIT_SUCCESS和EXIT_FAILURE分别表示程序的终止是成功还是失败。这两个宏都是在<stdlib.h>中定义的。
                exit(0);作用是关闭所有文件,正常终止正在执行的程序。
                exit(非0);作用是关闭所有文件,非正常终止正在执行的程序。
           
            (2)void abort( void );用于不正常的终止一个正在执行的程序,还会引发SIGABRT信号。
           
            (3)void atexit( void (func)(void) );把一个函数注册为推出函数,当程序要正常终止时(或者由于调用exit,或者由于main函数返回),退出函数将被调用。       

        ●字符转换
            1、int tolower(int ch); 返回对应的小写

            2、int toupper(int ch); 返回对应的大写
           
        ●环境
            1、char *getenv( char const *name );函数在由编译器定义的键值对中查找一个特定的名字,返回一个指向其对应值的指针。

        ●执行系统命令
            1、void system( char const *command );把参数字符串传递给宿主操作系统,这样它就作为一种命令,由系统的命令处理器执行。如果传个NULL,可以判断命令处理器是否存在。

    ★<ctype.h>字符分类
        1、int isspace(int c) 判断c为空白符(空格' ',换页'/f',回车'/r',换行符'/n'、制表符'/t',垂直制表符'/v'),如果是就返回真非0,否则返回假0。
       
        2、int isdigit(int c) 判断是否是数字,如果是返回真,否则返回假。十进制 0~9
           int isxdigit(int c) 十六进制数判断       

        3、int isalpha(int c) 判断c是不是字母(包括大小写),是就返回真,否则返回假。
           
        4、int islower(int c) 小写字母判断 a~z

        5、int isupper(int c) 大写字母判断 A~Z

        6、int isalpha(int c) 字母判断 a~z或A~Z

        7、int isalnum(int c) 字母或数字 a~z或A~Z或0~9
       
        8、int ispunct(int c) 标点符号,任何不属于字母或数字的可打印字符

        9、int isgraph(int c) 任何图形字符

        10、int isprint(int c) 任何可打印字符

    ★<time.h>日期和时间函数
        1、clock_t clock( void );返回从程序开始执行起处理器所消耗的时间。
        2、time_t time( time_t *returned_value); 参数为NULL时返回从1970年1月1日00:00:00起的秒数。如果参数不为NULL则时间存到此参数所指向的内存中。
        3、struct tm *localtime( time_t const *time_value );把时间转换为当地时间值。
            此时:tm可以访问结构中的tm_year年(从1990年之后的年数,所以实际年份要加1990)、tm_mon月(从0开始)、tm_wday星期、tm_yday1月1日之后的天数、tm_mday当月日期、tm_hour小时、tm_min分钟、tm_sec秒、tm_isdst夏令时标志。
        4、time_t mktime( struct tm *tm_ptr );把一个tm结构转换成一个time_t值,可用于判断某个特定的日期属于星期几。

    ★<setjmp.h>非本地跳转,它们跟goto不同,因为不局限于一个函数内部的跳转。
        1、int setjmp( jmp_buf state );
        2、void longjmp( jump_buf state, int value );
   
    ★<stdarg.h>可变参数表
        1、va_list ap类型用于声明一个变量,该变量将依次引用个参数。
       
        2、va_start(ap,fmt)将ap初始化为指向由fmt这个参数开始之后的第一个无名参数的指针。
       
        3、va_arg(ap,int)返回ap指向的参数,并让ap移向下一个参数,类型名(int)决定返回对象的类型。
       
        4、va_end()完成一些必要的清理工作。
        ●打印可变参数列表(一下函数必须包含stdarg和stdio两个头文件)
            int vprintf( char const *format, va_list arg );
            int vfprintf( FILE *stream, char const *format, va_list arg );
            int vsprintf( FILE *buffer, char const *format, va_list arg );
            它们跟对应的标准库函数功能基本相同,只是使用了可变参数列表。在调用这些函数之前arg参数必须使用va_start初始化,但都不需要调用va_end)。

    ★<signal.h>信号处理
        1、int raise( int sig );显式的引发一个参数指定的信号。
        2、void ( *signal( int sig, void ( *handler )( int ) ) ) )( int ); signal函数返回一个指向该信号以前的处理函数的指针。
   
    ★<assert.h>断言,就是声明某种东西应该为真。
        1、void assert( int expression );这是一个在ANSI C中实现的宏,而并非真正的函数。此原型说明了assert宏的用法。
        2、NDEBUG 在头文件signal.h被包含之前,定义这个宏可以消除所有断言。

    ★<stddef.h>
        1、offsetof(type, member); type是结构体类型,member是结构体的一个成员,此宏可以返回member在结构体内存的起始位置的偏移量。


■这是size_t的类型说明:typedef unsigned int size_t;

好了,暂时就写到这,以后深入学习后,还会更新的,我估计在以后的工作中会经常翻阅此贴和MSDN,大家也可以顺便做个参考。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值