牛骨文教育服务平台(让学习变的简单)
博文笔记

C 学习笔记

创建时间:2017-09-20 投稿人: 浏览次数:325

源自:《C语言入门经典(第4版)》(美)lvor Horton. 著  杨浩.  译  北京.清华大学出版社

1.C程序创建4个基本步骤:编辑,编译,链接,执行。
   编辑过程就是创建和修改C程序的源代码,我们编写的程序指令称为源代码。C编译器带一个编辑器,提供编写、管理、开发与测试程序的环境,有时也称为“集成开发环境(IDE)”。
   在UNIX或Linux上,最常用的的文本编辑器是vi,也可以使用emacs编辑器。
2.编译器将源代码转换成机器语言,产生源文件,编译器输出结果称为对象代码(object code),存放他们的文件称为对象文件(object file)。这些文件扩展名在Windows环境中是.obj,在Linux/UNIX环境中是.o。
3.链接器(Linker)将源代码文件中由编译器产生的各种模块组合起来,再从C程序库中添加必要的代码模块,将其组合成一个可执行的文件。如果程序太大,可拆成几个源代码文件(分别编译),再用链接器连接起来,组成程序的源文件通常会用同一个项目名称集成,这个项目名称用于引用整个程序。链接成功后产生一个可执行文件,Windows环境下,可执行文件的扩展名为.exe。UNIX环境下,没有扩展名,但它是一个可执行的文件类型。
4.int main(void)
   关键字(也称保留字)int表示main()函数返回一个整数值,执行完main()函数后返回的整数值表示返回给操作系统的一个代码,表示程序的状态。
   void表示没有给函数main()传递任何数据
5.return 0;
return语句结束main()函数的执行,把0返回给操作系统,从main()函数返回0表示,程序正常终止,返回非0值表示异常。
6.联合测试各个程序模块,称为集成测试(integration testing)
7.计算机执行程序时,组成程序的指令和程序所操作的数据都必须存储到某个地方,即执行程序时存储程序及数据的地方。
这个地方就是机器的内存,也称为主内存(main memory),或者随机访问存储器(Random Access Memory,RAM)
    计算机中还有另一种存储器,即只读存储器(Read-Only Memory,ROM),ROM不能修改,只能读取其内容,或让计算机执行包含在ROM中的指令。ROM中的信息是在制造机器时放进去的,主要是一些程序,用来控制连接到计算机上的各种设备的运作,这些程序称为,计算机的基本输入输出系统(Basic Input/Output System,BIOS)
8.变量是计算机里一块特定的内存,是由一个或多个连续的字节所组成,每个变量都有一个名称,可以用该名称表示内存的这个位置,以提取它包含的数据或存储一个新数值。
9.在计算机中,所有的值都存储为二进制数。
10.变量名可以包含的字符数取决于编译器,遵循C语言标准的编译器至少支持31个字符,不超过该长度即可。
11.相同类型的不同变量总是占据相同大小的内存(字节数)
12.计算机的内存组织为字节。
13.VC++6.0规定int型类型,等价于long int型,占4字节。
14.初始化浮点型变量,float radius = 0.0f; 如果没有f的话,则它的类型就是double
15.#define语句中的标识符都是大写,例,#define PI 3.14159f (定义一个常量,或者采用,const float pi = 3.14159f; 注:变量名前加上const关键字,可以固化变量的值)
16.强制类型转换,要把变量从一种类型转换为另一种类型,应把目标类型放在变量前面的括号中。
17.每个无符号整数类型的级别都与对应的带符号整数类型(类型指的是,值域类型)相同。例如,unsigned int类型的级别与int类型相同。
18.char类型的变量有双重性,既可解释为一个字符,也可解释为一个整数,例如,char character = 67;等价于char character = "C"; character = character + 3;可以对char类型的值进行算术运算,同时仍把它当做一个字符。
19.math.h头文件包含各种数学函数的声明,所有的函数都返回一个double类型的变量。
20.if语句的控制表达式要生成一个布尔结果,所以编译器要将If表达式的数值结果转换成_Bool类型。
21.Long test = 0L;//如果使用0代替0L,编译器就会插入代码,将int类型的0转换为long类型。
22.a的ASCII码(十进制)值是97,A的ASCII码值是65.
23.运算符说明,“.”按对象选择成员,“->”按指针选择成员,Sizeof,表达式或类型的字节数。
  (type)强制类型转换,例如(int)或(double)
   “&”是按位与,“&&”是逻辑与
   “^”是按位异或,“|”是按位或,“||”是逻辑或
24.tolower()函数,将ch的值转换为小写。
25.按位运算符,只能用于整数类型。按位与&、按位或|运算符的一个主要用途是测试并设置整数变量中的各个位,使用各个位存储涉及二选一的数据。
     定义“掩码”,即首先定义一个值,用于选择需要的位,在掩码中,希望保持不变的位置上包含1,希望舍弃的位置上包含0,接着对这个掩码与选中位的值,执行按位与操作。
26.%运算符只能用于整数。long是long int的缩写,占4个字节,P48页
27.for(int i=1;i<=count;sum+=i++);等价于for(int i=count;i>=1;sum+=i--);
28.for(;;);循环永不停止,经常结合break;语句使用
29.每次调用rand()函数,它都会返回一个随机整数,这个值在0到<stdlib.h>定义的RAND_MAX之间,由rand()函数产生的整数称为伪随机数。P154页
30.toupper(),用来将字符c转换为大写英文字母。如果括号内的字符是小写英文字母,则返回对应的大写字母;否则返回原来的值。
31.键盘缓冲区是用来存储键盘输入的内存,scanf()函数是在键盘缓冲区查找输入数据,而不是直接从键盘上读取数据。
     标准输入和输出-键盘和屏幕-有两个缓冲区:一个用于输入,另一个用于输出,标准输入输出流分别称为stdin和stdout。要指定键盘输入缓冲区,只需使用名称stdin。标准库函数fflush()就是用于清除缓冲区的。清除输入缓冲区的内容可以使用:fflush(stdin);
32.“生成一个随机数”相关例程,没有细看。P168页始
33.数组是相同数据类型的元素按一定顺序排列的集合。
34.格式指定符%p,来输出变量的地址,即内存地址,其值为十六进制,内存地址一般是16,32,或64位,地址的大小决定了可以引用的最大内存量。
35.数组名,指定了存储数据项的内存区域地址,结合地址和索引值,就可以找到每个元素,索引值表示各个元素与数组开头的偏移量。
   数组元素的地址是数组开始的地址加上元素的索引值乘以数组中每个元素类型所需的字节数。
   分配给每个元素的内存量取决于数组所含的变量的类型。
36.字符串的长度永远比字符串中的字符数多1.
   printf()函数遇到第一个空字符时,就会停止输出。即在遇到第一个时,就表示字符串已经结束了。
37.要引用存储在数组中的字符串时,只需使用数组名即可。
38.用while循环确定字符串长度,P215页
39.sizeof是C/C++中的一个操作符(operator),返回一个对象或类型所占的内存字节数,即判断数据类型或者表达式长度符。例如P217页
     标准库中strlen()函数可以确定字符串的长度。
40.要对字符串执行算术赋值操作,必须逐个元素地把一个字符串复制到另一个字符串中。
41.二维数组存储字符串,第一维指定字符串的数目,数组的每一行都用来存储一个字符串。P218页
42.size_t是strlen()函数的返回值,也是运算符sizeof的返回类型,(如果返回类型不同,则是编译器不同的原因)类型size_t定义为一个无符号的整数类型unsigned int
     可移植性高的代码:
                    size_t count2 = 0;
                    count2 = strlen(str2);
43.字符串的比较是基于它们的字符码。
44.显示使用&运算符:scanf("%s",&word1[0]);
   &word1[0]等于word1
45.指针是含有地址的变量,它含有内存中另一个包含数值的位置的引用。
46.函数strchr()在字符串中搜索给定的字符,第一个参数是要搜索的字符串(char数组的地址),第二个参数是要查找的字符,这个函数会从字符串的开头开始搜索,返回在字符串中找到的第一个给定字符的地址,没有找到的话,返回NULL,即这个指针没有指向任何对象。用法见P227页
47.strstr()函数。查找一个字符串中的子字符串,返回找到的第一个子字符串的位置指针,如果找不到匹配的子字符串,就返回NULL,第一个参数是要搜索的字符串,第二个参数是要查找的子字符串。
48.标准库函数gets()将输入的字符串读入数组buffer(在字符串的末尾会自动附加一个""字符)。
     scanf()接收键盘输入,但不适合读入字符串,因为它会将空格解释为输入值的末尾,而gets()函数的优点是可以从键盘读入所有字符(包括空白),直到按下回车键为止,然后将字符串存储到其参数指定的区域中。
     如果gets()函数在读入输入时发生错误,就会返回NULL(通常它会返回传给它的参数的地址)       程序中因此判断举例:P231页
     gets()函数的缺点是,它会读取任意长度的字符串,并存储在buffer中,但没有检查buffer是否有足够的空间存储该字符串,所以程序可能崩溃。为避免这种情况,可以使用fgets()函数,它允许指定输入字符串的最大长度。这个函数可用于任意种类的输入字符串,而gets()只能读取标准输入流stdin;所以还必须指定fgets()的第三个参数,说明要读取的输入流。  
    如果从键盘上读取字符串,fgets()读取的字符串比gets()读取的字符串多一个字符,另外,在输入时按下回车键,gets()读取的输入会附加一个空字符串,而fgets()读取的输入会附加 。   
    程序中因此判断举例:P231页
49.fgets()函数从键盘输入中读取数据,第三个参数则为stdin(标准输入流)stdin默认对应键盘输入。
50.宽字符串,存储在wchar_t类型的数组中,wchar_t字符占用2个字节,要使用printf()将字符输出到屏幕上,必须使用%S格式指定符,而不是用ASCII字符串的%s,如果使用了%s,printf()函数就假定字符串包含单字节字符,出错。
51.if(!strcmp(fgets(buffer,BUFFERSIZE,stdin),endstr))
   fgets()函数返回一个指针,该指针指向传送为第一个参数的字符串,所以可以把fgets()用作strcmp()函数的参数,比较读取的字符串和endstr。
  “字符串和文本的应用”设计程序,见P239页,步骤一有问题,while循环前,需要先清空text,可用“text[0]=""”或“memset(text,0,sizeof(text))”
程序见“lianxi”
   isalnum()函数对字母或数字返回true
52.可以存储地址的变量称作指针(pointers)。每个指针都和某个变量类型相关联,也只能用于指向该类型的变量。
   类型名void表示没有指定类型,所以void*类型的指针可以包含任意类型的数据项地址。类型void*常做参数类型,或以独立于类型的方式处理数据的函数的返回值类型,任意类型的指针都可以传送为void*类型的值,在使用它的时候,再将其转换为合适的类型。
53.int *pointer;//声明一个指向int类型变量的指针。pointer变量的类型是int *,它可以存储任意int类型变量的地址。
   int *pointer = NULL;//初始化pointer,使它不指向任何对象。NULL是标准库中定义的一个常量,对于指针它表示0,NULL是一个不指向任何内存位置的值,这表示,使用不指向任何对象的指针,不会意外覆盖内存。
54.int number = 0;要输出变量number的地址,应使用输出格式指定符%p,它以十六进制格式输出内存的地址。
55.*运算符的作用是访问存储在pointer(指针变量)中的地址的数据,*运算符称为间接运算符,有时也称取消引用运算符。
56.声明指针(比如:long *pnum = NULL;)时,一定要初始化它们,使用未初始化的指针存储数据项是很危险的,在使用指针存储一个值时,谁也不知道会覆盖什么内容。
57.运算符++和一元运算符*(和一元运算符&)的优先级相同,且都是从右到左计算的
58.指向常量的指针,用const关键字指定,如:                                  long value = 9999L;const long *pvalue = &value;
   定义后,pvalue指向的值不可更改,但是可以对value进行任意操作。
   即改变了pvalue指向的值,但不能使用pvalue指针做这个改变。指针本身不是常量,所以仍可以改变它指向的值。
59.在C/C++中,声明常量指针两种形式:
   (1)const int *p;
   (2)int const *p;
注:定义int a,b;const int *p=&a;
       *p不可更改,p可修改,如,p=&b
       在使用字符串处理函数时,函数的声明,它们的参数一般声明为常量指针,比如int strcmp(const char *str1, const char *str2);             

识记:const的位置在指针声明运算符*的左侧。只要const位于*的左侧,无论它在类型名的左边或右边,都声明了一个指向常量的指针,叫做常量指针。
60.在C/C++中,定义指针常量:
        int a;
        int *const p=&a;
注: 因为指针常量是一个常量,声明的时候要赋初始值。赋值后,这个常量再也不能指向别的地址。虽然指针常量的值不能变,但是它指向的对象是可变的,我们并没有限制它指向的对象是常量。
        *p可以修改,如*p=8; p不可以被修改
61.const *  表示带*运算对象的是常量,也就是*p 不可变 (暗示p可变,p不带‘*’ 嘛)
   * const 变量名  表示变量名是常量  也就是p不可变 (暗示*p可变,const没有修饰‘*“ 嘛)
62.在C/C++中,声明指向常量的指针常量:
     const int a = 25;
     const int *const b = &a;
注: *p 和 p均不可改变
63.multiple[n]与*(multiple+n)是相同的。P260页       数组名称是一个固定的地址,而不是一个指针。
64.对于二维数组board[3][3],board、board[0]、&board[0][0]的数值相同
   注:
       board是整个二维数组的地址
       board[0]是数组中第一个有3个元素的一维数组的地址
       &board[0][0]是数组中某个元素[0][0]的地址
       三者数值相等但是其实不同。
       如果使用board获取第一个元素的值,就需要使用两个间接运算符**board,使用一个间接运算符*board,就只会得到子数组(子一维数组)的第一个元素。或者直接引用board[0][0]
    怎么理解*(*board+i),得到一个数组中所有元素的值。
注:*board相当于board[0],因此*board+i会得到数组中偏移量为i的元素的地址,*(*board+i)会得到这个地址中的值。(因为board是二维数组,故相当于char **,要用双*号)
    参考P263-265页,红色笔写——例1和例2
65.在计算机内存中,是没有多维数组的概念的。
66.只有使用指针,才能动态分配内存。
67.在程序的执行期间分配内存时,内存区域中的这个空间称为堆(heap)。还有另外一个内存区域,称为堆栈(stack)。其中的空间分配给函数的参数和本地变量。在执行完该函数后,存储参数和本地变量的内存空间就会释放。堆中的内存是由程序员控制的。
   堆内存是用malloc或new分配的内存空间,栈是用关键字定义出变量的内存空间。有些时候必须用堆内存,如:链表。堆最好是用来存放大数据,而栈本身就小,但速度快。
68.运行时分配内存的最简单的标准库函数是malloc()。使用这个函数时,需要在程序中包含头文件<stdlib.h>。使用malloc()函数需指定要分配的内存字节数作为参考。这个函数返回所分配内存的第一个字节的地址。因为返回的是一个地址,所以可使用指针。
   动态内存分配实例:
        int *pNumber = (int *)malloc(100);//请求100个字节的内存
注:(1)将这个内存块的地址赋予pNumber,指向所分配的100个字节的第一个int       的位置,该内存块可以保存25个int值,每个int占4个字节。
    (2)类型转换(int *)将函数返回的地址转换成int类型的指针。
           因为malloc()是一般用途的函数,可以为任何类型的数据分配内存,这个函数不知要这个内存做何用,所以返回一个void类型的指针,即 void*。类型void*的指针可以指向任意类型的数据,然而不能取消对void指针的引用,因为它指向未具体说明的对象。许多编译器会把malloc()返回的地址自动转换成适当的类型,且不会伤害具体指定的对象。
    (3)可以请求任意数量的字节,字节数仅受制于计算机中未用的内存以及malloc()的运用场合,如果不能分配请求的内存,malloc()会返回一个 NULL指针,这个指针等于0.因此要先用if语句检查请求动态分配的内存是否已分配,再使用它。
          if(pNumber == NULL)
          {
           /*Code to deal with no memory allocated */
           }  
     如果指针是NULL,则可以执行“中止程序”指令。
69.分配内存可使用sizeof运算符。处理int数据类型:
 pNumber = (int *)malloc(75*sizeof(int));

//给75个int类型数据项分配内存
//等式右侧第二个“*”是乘号
//malloc()函数返回的值是一个void指针,因此必须用表达式(int *)将它转换 //成所需要的类型。   见P421页
    sizeof是一个运算符,返回一个size_t类型的无符号整数(有些变量声明为此类型,是为了和strlen()函数返回的类型兼容,以避免编译器产生警告信息)该整数是存储它的参数需要的字节数,将关键字如int或float等作为参数,返回存储该类型的数据项所需要的字节数。它的参数也可以是变量或数组名,把数组名作为参数时,sizeof返回存储整个数组所需的字节数。实例见P271页
70.在C/C++中,%u表示无符号10进制整数
     %d或%i表示有符号10进制整数,后者可以自动将输入的8进制或16进制转换为10进制。
71.malloc()函数的参数是size_t类型,所以size_t对应的整数类型限制了可以指定的字节数。
72.在<stdlib.h>头文件中声明的calloc()函数与malloc()函数相比有两个优点:

  第一,把内存分配为给定大小的数组;
  第二,初始化所分配的内存,所有位都是0。
       calloc()函数需要两个参数:数组的元素个数和数组元素占用的字节数,这两个参数的类型都是size_t。该函数不知道数组元素的类型,因此所分配区域的地址返回为void *类型。
       使用calloc()为包含75个int元素的数组分配内存:
       int *pNumber = (int *) calloc(75,sizeof(int));
  如果不能分配所请求的内存,返回值为NULL,也可以检查分配内存的结果,这非常类似于malloc(),但calloc()分配的内存区域都会初始化为0。
73.堆上分配的内存会在程序结束时自动释放,使用完内存后应该立即释放。当动态分配了一些内存时,没有保留对它们的引用,就会出现内存泄漏,此时无法释放内存,常发生在循环内部。
74.要释放用malloc()或calloc()分配的内存,必须使用函数返回的引用内存块的地址。要释放动态分配的内存,而该内存的地址存储在pNumber指针中,可使用:                 
      free(pNumber);
      free()函数的形参是void*类型,所有指针类型都可以自动转换为这个类型,所以可以把任意类型的指针作为参数传送给这个函数。只要pNumber包含分配内存时malloc()或calloc()返回的地址,就会释放所分配的整个内存块。
      如果给free()函数传送一个空指针,该函数就什么也不做,应避免两次释放相同的内存区域,如果多个指针变量引用已分配的内存,就有可能两次释放相同的内存。
75.realloc()函数可以重用前面通过malloc()或calloc()(或realloc())分配的内存。函数需要两个参数:一个是指针,它包含前面调用malloc()、calloc()或realloc()返回的地址,另一个是要分配的新内存的字节数。
   realloc()函数释放第一个指针参数引用的之前分配的内存,然后重新分配该内存区域,以满足第二个参数指定的新请求。显然,第二个参数的值不应超过以前分配的字节数,否则,新分配的内存将与以前分配的内存区域大小相同。
76.size_t是标准C库中定义的,应为unsigned int,在64位系统中为:long unsigned int
   一个基本的无符号整数的C/C++类型,它是sizeof操作符返回的结果类型,该类型的大小是选择,因此,它可以存储在理论上是可能的任何类型的数组的最大大小。
   size_t类型通常用于循环、数组索引、大小的存储和地址运算。
77.使用动态分配的内存的基本规则:
   (1)避免分配大量的小内存块,分配堆上的内存有一些系统开销,因此分配小的内存块比分配几个大的内存块的系统开销大;
   (2)仅在需要时分配内存。只要使用完堆上的内存块,就释放它;
   (3)确保释放已分配的内存。在编写分配内存的代码时,就要确定在代码的什么地方释放内存;
   (4)在释放内存之前,确保不会无意中覆盖堆上分配的内存的地址,否则程序就会出现内存泄漏,在循环中分配内存,需特别小心。
78.指针只是一个存储另一个内存位置的地址的变量。
   要存储字符串,需要分配一些内存,可以声明一块内存,来存储字符串数据,然后使用指针追踪这块存储字符串的内存。
79.getchar()函数,一次只读取一个字符,可以控制何时停止读入字符,从而确保不会超过为存储输入而分配的内存。
   使用getchar()读取字符串,可以对输入过程进行很多控制。这种方法并不仅限于读取字符串,还可以用于读取逐个处理字符的所有输入过程,可以从输入中删除空格,或者查找特定的字符。例如用于分隔各个输入值的逗号。
80.处理多个字符串时,可以在堆上使用指针数组存储对字符串的引用。
   创建一个指针数组:
   char *pS[3] = { NULL };//声明了一个数组pS,它包含3个指针。初始化列表中只有一个值NULL,它将任意大小的指针数组中的所有元素都初始化为NULL。
81.使用全局变量指定数组大小时,该变量必须声明为const,才能用来指定数组的大小,因为数组的大小只能用常量表达式指定。对应P280页
82.char *pbuffer = buffer;//pbuffer指针用buffer数组中的第一个字节的地址初始化。
83.free()函数与malloc()函数是互补的,它释放了malloc()分配的内存,它只需要把所分配的内存指针作为参数,虽然内存在程序结束时会自动释放,但是内存最好在不需要时立即释放,当然,一旦用这个方式释放内存后,就不能再使用它,所以立刻将该指针设定成NULL。
   例如:pS[i] = (char*)malloc(pbuffer-buffer);
         ....
         ....
         free(pS[i]);
         pS[i] = NULL;
84.程序解析“字符串输入的扩展”P282页,“使用指针对字符串排序”P286页
85.指针错误会产生灾难性的结果。如果使用一个没有指定地址值的未初始化指针存储值,该指针使用的地址就是存储在该指针位置的任何内容,这可能是内存中的任何一个位置。
86.sizeof()与strlen()的区别联系:
 (1)sizeof是操作符,不是函数,后面跟的是“类型”,必须加(),如果是“变量名”,可以不加()。它计算的是系统为它后面的变量或类型名分配的空间大小,无论其中是否存储了有用数据;
    strlen()是库函数,()中必须是字符串指针或字符串常量,它返回的是检测对象中第一个""前的字符个数,不含""。而sizeof则包括结束符""。
例子:
    #include "stdio.h"
    #include "string.h"
    int main(void)
    {
       char s[50]="1234",p[100]={"f","j","k","d","","1","2","3"};
       printf("sizeof(s) = %d ",sizeof s);//这里s没有加(),也没错
       printf("strlen(s) = %d strlen(p) = %d ",strlen(s),strlen(p)); //检测p时,遇到第一个""就结束了
       return 0;
     }
 (2)两者都是返回字节数。具体来说,前者返回的是字节数,后者返回的字符数
 (3)本质区别:sizeof获取的是类型占内存的大小,strlen获取字符串内容长度 比如:
     处理静态数组:
     char str[20]="0123456789";
     int a=strlen(str);//a=10;>>>strlen计算字符串的长度
     int b=sizeof(str);//b=20;>>>sizeof计算的则是分配的数组str[20]所占的内存空间的大小,不受里面存储的内容改变。
     处理指针:
     char *ss = "0123456789";
sizeof(ss)的返回值为4,因为ss是指向字符串常量的字符指针,sizeof获得的是一个指针的值所占的空间,长整型,占4;
sizeof(*ss)的返回值为1,因为*ss是第一个字符,获得字符串的第一位"0"所占的内存空间,是char类型的,占了1位;
strlen(ss)的返回值为10,获得的是字符串的长度。
 (4)sizeof的功能是:获得保证能容纳实现所建立的最大对象的字节大小。
     由于在编译时计算,因此sizeof不能用来返回动态分配的内存空间的大小。实际上,用sizeof来返回类型以及静态分配的对象、结构或数组所占的空间,返回值跟对象、结构、数组所存储的内容没有关系。    
    参数可以是数组、指针、类型、对象、函数等。具体而言,当参数分别如下时,sizeof返回的值表示含义如下:
    数组--编译时分配的数组空间大小;
    指针--存储该指针所用的空间大小(存储该指针的地址的长度,是长整型,应该为4);
    类型--该类型所占的空间大小;
    对象--对象的实际占用空间大小;
    函数--函数的返回类型所占的空间大小,函数的返回类型不能是void。
    strlen(...)是函数,要在运行时才能计算。参数必须是字符型指针(char*)
当数组名作为参数传入时,实际上数组就退化成指针了。
    功能是:返回字符串的长度。该字符串可能是自己定义的,也可能是内存中随机的,该函数实际完成的功能是从代表该字符串的第一个地址开始遍历,直到遇到结束符NULL,返回的长度大小不包括NULL。
87.以“冒泡排序”的方式,使用指针处理字符串和其他类型的数据,可以将基本数据(此处是字符串)以任意顺序放在一块内存里,然后只需改变指针,就可以用任意顺序处理它们,而完全不用移动它们。见P289页,上面的例子
88.scanf()函数不允许读入含有空格的完整字符串,而是在第一个空白字符处停止读取。而fgets()函数则可以整行读入,包含空格。
89.while(strcmp(fgets(input,BUFFER_LEN,stdin),"quit ")!=0)
   函数strcmp()的参数是一个指向字符串的指针,而函数fgets()会返回一个指向用户输入的字符串的指针,此处是&input[0]。
90.大多数计算机的键盘缓冲区是255个字符(这也是按下回车键之前,所能输入的最大字符数),因此,设定从键盘输入的字符串长度可为256。
91.为字符分析函数包含<ctype.h>头文件,为使用atof()函数包含<stdlib.h>头文件,atof()函数将一个字符串参数转换成浮点数值。
92.变量在一个块内声明时创建,在这个块结束时删除,这种变量称为自动变量,因为它们是自动创建和删除的。
   给定变量可以在某个程序代码块中访问和引用,这个程序代码块称为变量的作用域。
93.函数main()是C程序执行的起点,所有的程序都必须编写函数main()
94.函数头定义了函数的名称、函数参数(换句话说,即调用函数时传给函数的值的类型)和函数返回值的类型;
   函数体决定函数对传给它的值执行什么操作。
   函数体内可以没有语句,但是大括号必须有。如果函数体内没有语句,返回类型必须是void,此时函数没有任何作用。void表示“不存在任何类型”,即表示函数没有返回值,没有返回值的函数必须将返回类型指定为void,而返回类型不是void的函数都在函数体中有一个return语句,返回一个指定返回类型的值。
   括号中的参数是变元值的占位符,调用一个函数时,必须指定参数的值。术语“参数”表示函数定义中的一个占位符,指定了调用函数时传送给函数的值的类型。参数包含在函数体内,用来表示函数执行时使用的数据类型和名称。术语“变元”表示调用函数时提供的对应于参数的值。
95.调用函数时给它们指定的值称为“变元”,然后使用这些参数在函数体中编写计算操作,当函数执行时,参数使用变元的值。
   
     如果在表达式中使用函数,或函数在赋值语句的右侧使用,则函数的返回值会取代该函数。函数的返回值可以指定为C语言中任何合法的类型,包括指针。
      返回类型也可以是void*,表示指向void的指针。此时,返回值是一个地址值,但是没有指定类型,希望返回一个能灵活用于各种目的的指针时,就可以使用这个类型,比如分配内存的malloc()函数。
96.函数声明是一个定义函数基本特性的语句,它定义了函数的名称、返回值的类型和每个参数的类型。函数声明和编写的函数头一模一样,不过语句后面要加分号。
   函数声明也叫做函数原型,提供函数的所有外部规范。
   函数原型能使编译器在使用这个函数的地方创建适当的指令,检查是否正确的使用它。
97.函数原型一般放在源文件的开头出,而且在所有函数的定义之前。另外,在源文件中,函数原型在所有函数的外部,函数的作用域是从其声明处开始一直到源文件的结尾。因此无论函数的定义放在什么地方,源文件中的任意函数都可以调用该文件中的其他函数。
   main()函数不需要函数原型,因为在程序开始执行时,这个函数会由主机环境调用。
98.类型const char**与类型const char*[]相同,const char*[]是const char*类型的数组。
99.exit()函数可以终止程序,返回一个状态值给操作系统。
   exit(0):程序成功结束
   exit(1):程序失败
100.使用指针传输数据:“排序字符串”实例。P320页
101.调用函数必须释放malloc()分配的内存,最好给函数传送两个变元,一个变元是初始化变量的地址,另一个是存储新的变量的地址,这样调用函数就可以支配内存了。
    将执行期间分配的内存和释放内存分开,有时会造成内存泄漏。在循环中重复调用的函数动态分配内存后,却没有释放它,就会造成内存泄漏。结果越来越多的可用内存被占用,当没有内存可用时,程序就会崩溃。应尽可能使分配内存的函数在使用完内存后就释放它。如果不能由函数释放内存,就编写代码,释放
动态分配的内存。
    在函数中声明的所有变量都是函数的本地变量。
102.函数指针是指向函数的指针变量,“函数指针”本身首先应是指针变量,只不过该指针变量指向函数,正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。
    C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。函数指针有两个用途:调用函数和做函数的参数。
    指针函数是指带指针的函数,即本质上是一个函数,函数都有返回类型(如果不返回值,则为无值型),只不过指针函数返回类型是某一类型的指针。
    首先它是一个函数,只不过这个函数的返回值是一个地址值。函数返回值必须用同类型的指针变量来接受,也就是说,指针函数一定有“函数返回值”,而且,在主调函数中,函数返回值必须赋给同类型的指针变量。
    函数指针本质是指针,指向一个函数的指针;而指针函数本质是函数,函数的返回值为指针。
103.变量在执行到定义它的块尾时就超出了作用域,它们在堆栈上分配的内存会被释放,以供另一个函数使用。这些变量称为自动变量。
104.关于“静态变量”:
  (1)虽然它在函数的作用域内定义,但当执行退出函数后,这个静态变量不会删除;
  (2)自动变量每次进入作用域时,都会初始化一次,但是声明为staic的变量只在程序开始时由编译器初始化一次。
  (3)可以在函数内创建任何类型的静态变量。
  (4)静态变量只能在声明它的范围内可见,不能在该作用域的外部引用。
105.常量在程序文件的开头声明,所以常量位于组成程序的所有函数的外部,采用这种方式声明变量,这种变量称为全局变量。
106.在C/C++中,全局变量如果没有初始化,则默认为0,而局部变量如果没有初始化,则是遗留在内存中的垃圾数据,或者是一个很大的负数,局部的int默认的是-858993460,即0xcccccccc
    VC的DEBUG会把未初始化的指针自动初始化为0xCCCCCCCC,汉字“烫”的编码正好是1100110011001100(常见于忘记赋值或数组越界的情况),而全局变量链接时已分配空间,程序运行时,操作系统的加载器,负责把链接器分配给全局变量的虚拟内存空间,映射到一个初始化为零的页面,即初始化为0。
    全局和静态的默认初始化都是靠加载机制实现的,另外,未初始化的符号在目标文件的bss段中,而初始化的符号在data段中。
    局部变量存在于(堆栈)中,全局变量存在于(静态区)中,动态申请数据存在于(堆)中。
107.当自动变量或者静态变量和全局变量同名时,它们会隐藏同名的全局变量。如果已经定义全局变量,若调用的函数里面没有定义同名的自动变量,而用到同名的变量时,那么就使用该全局变量。P346页
108.函数调用自己称为递归。
109.标准库<stdarg.h>头文件提供了通常实现为宏的例程,宏的外观和操作都类似于函数。va_list在该头文件中有定义,用于存储支持可变参数列表的例程所需的信息。
    变元个数可变的函数——P349
110.main()函数是程序执行的起点,该函数有一个参数列表,在命令行中执行程序时,可以给它传递变元。main()函数可以有两个参数,也可以没有参数。
       main()函数有参数时,第一个参数的类型是int,表示在命令行中执行 main()函数的参数个数,包含程序名在内;第二个参数是一个字符串指针数组。
比如:int main(int argc,char *argv[])    P353页
       void main()表示接受任何参数且无返回值;int main(void)表示整数类型主函数(无类型);int main()表示接受任何数量的参数。
111.调用两个在<stdlib.h>头文件中声明的标准库函数可以结束程序:
abort();//立即终止程序,表示程序操作是非正常结束
exit();//可使程序正常结束,该函数需要一个整数变元返回给操作系统。
           exit(0);//正常结束
           exit(1);//非正常结束
    以上两行语句可以在程序的任何地方使用
    也可以在main()函数中用一个整数执行return语句,结束程序。如:return 1
    return语句在main()函数中有特殊的意义(其他函数没有),这相当于使用return语句中指定的值调用exit()函数,return语句中的值会返回给操作系统。
112.标准头文件                 函数
    <stdio.h>               输入输出函数
    <string.h>              字符串处理函数
    <stdlib.h>              内存分配函数
    <stdbool.h>             bool类型和布尔值true和false
    <ctype.h>               字符分类函数
    <math.h>                数学浮点函数
    <complex.h>             支持复数
    <stdarg.h>              支持变元个数可变的函数的宏
    <wctype.h>              宽字符转换函数
详见P355页
113.有两个工具可以使编译器生成性能更高的代码:内联函数声明(函数头使用inline关键字来指定)和使用restrict关键字
114.类似“五子棋”游戏编程,见P357页
115.C语言没有输入输出能力,所有这类操作都由标准库中的函数(独立于设备之外)提供。
    信息是“字符流(也称文本流)”,scanf()函数可以从任何地方接收信息,printf()函数可以将数据输出到任何能接收字符流的地方去。    
     C语言中的每个输入源和输出目的地都称为流(stream)输入流是可读入程序的数据源;而输出流是程序输出数据的终点。
     流和设备的实体(如屏幕或键盘)相互独立。
     流和文件一一对应,而不是流和设备一一对应。一个流可以和磁盘里的一个文件关联,与文件关联的流是一个输入流,所以可以从这个文件中读取数据。如果这个流是输出流,就可以在这个文件中写入数据,如果这个流允许输入和输出,就可以对这个文件进行读取和写入。
116.字符流(也称文本流)和二进制流之间的差异:
    在字符流中传入的数据是一系列字符,可以根据格式规范由库例程修改;在二进制流中传入传出的数据是一系列字节,不能以任何方式修改。
117.标准输入流stdin上的键盘输入有两种形式:一种是格式化输入,主要由scanf()函数提供;另一种是非格式化输入,通过getchar()等函数接收原始的字符数据。
118.使用格式符%[]或%l[]读入的字符串必须只包含方括号内的字符,如果方括号中的第一个字符是^时,则读入的字符串不能包含方括号内^字符后面的任何字符。
    例如%[aeiou]读入的字符串只能包含元音,碰到不是元音的字符就停止输入,而%[^aeiou]读入的字符串不能包含元音,碰到元音就停止输入。
注:%[]指定符可以读入含有空格的字符串,但指定符%s不能。使用%[]指定符时,只需要在方括号中包含空格字符。  见P389页及例程“读入字符和字符串”
        控制字符串中的第一个字符是空格,那么scanf()会读入且忽略所有空格。
见P391页  
119.%n与其他格式说明符号不同,它不向printf传递格式化信息,而是令printf把自己到该点已输入的字符总数放到相应变元指向的整型变量中。
120.<stdio.h>头文件中的gets()函数可以将一整行的文本作为字符串读入。
    char *gets(char *str);
    该函数将连续的字符读入指针str所指的内存中,直到按下回车键为止。它会用终止字符""取代按下回车键时读入的换行符。其返回值与变元相同,即存储字符串的地址。
    gets()无法控制存储的字符数,必须创建一个具有足够空间来存储可能输入的最大长度字符串的数组。
121.  ...
      fgets(initial,sizeof(initial),stdin);
      fflush(stdin);//在第一个读取操作中,输入缓冲区中会剩下换行符,所以调用fflush()刷新stdin,并删除它。否则,换行符会成为name的输入
      fgets(name,sizeof(name),stdin);
      size_t length = strlen(name);
      name[length-1] = name[length];//name中空字符前的最后一个字符是换 行符,所以将终止字符复制到这个位置,以覆盖它。
122.对于字符串输入,使用gets()或fgets()通常是首选方式,除非要控制字符串的内容,此时可以使用%[]。当支持非标准的%[a-z]格式时,使用%[]指定符较为方便,但注意是非标准的,所以代码不像使用%[abcdefghijklmnopqrstuvwxyz]标准形式读取小写字符串那样是可移植的。
123.getchar()函数可从stdin中一次读入一个字符,在<stdio.h>中定义 int getchar(void);
    该函数不需要变元,它会返回从输入流中读入的字符,这个字符返回为int类型,并显示在屏幕上。
    “键盘的非格式化输入”、“读取和不读取字符”见P393页
124.常见转义字列:退格  f换页  换行(光标由当前位置换到下一行)   
    回车(用于打印机,屏幕输出中,就是光标移动到当前行的开头)回车换行符实际是两个字符,就是
125.在<stdio.h>头文件中声明的puts()函数也可以输出字符串。puts()函数与gets()函数互补。
       int puts(const char *string);
puts()函数接受字符串指针作为变元,将字符串写入标准输入流stdout。其字符串必须用字符""终止。它的参数是const,所以该函数不能修改传送给它的字符串。如果输出错误,返回一个负整数,否则就返回非负数。  

    puts()函数用于输出单行信息。
    puts()函数会自动在作为参数传入的字符串尾部添加" "字符,输出多行数据。
    putchar()函数将单个字符c输出到stdout上,并返回所显示的字符。它可以输出信息,一次显示一个字符(程序中可使用数组或指针,使地址不断地递增取值)    见P407页实例
126.数组的格式化输出:<stdio.h>声明的sprintf()函数与数组的格式化输入:<stdio.h>声明的sscanf()函数,互补。  见P407实例
       sprintf()函数:将格式化数据写入char类型的数组中
       sscanf()函数:可以在格式化字符串的控制下,从char类型的数组元素中读取数据。
       fprintf()函数可以将格式化输出传送到任何流中。
127.关键字struct能定义各种类型的变量集合,称为结构(structure),并把它们视为一个单元。
struct horse //声明了一个结构horse,它不是一个变量名,而是一个新的类型
{  //类型名称称为“结构标记符”或标记符名称
   int age;     //结构成员
   int height;  //结构成员
}Silver;//声明结构的一个实例变量Silver,它是一个horse类型的变量,变量名称为Silver,它包含两个结构成员:age和height
注:(1)结构每一个成员的初始化值都不能在“大括号”里面直接赋值,因为是定义horse类型的成员,不是在声明变量。结构类型是一种说明或一种蓝图,可用于定义该类型的变量,就这个例子而言,类型是horse

       (2)使用这种方法的最大缺点是,不能在其他语句中定义这个结构的其他实例。一般都是将“结构的声明”和“结构变量的声明”分开。
赋初值举例:
struct horse
{
    int age;
    int height;
    char name[20];
    char father[20];
    char mother[20];
} Dobbin = { 24,17,"Dobbin","Trigger","Flossie"};
128.关于引用结构的成员,结构变量的名称不是一个指针,所以需要特殊的语法访问这些成员。
    结构变量名称和成员名称间的句点是一个运算符,称为成员选择运算符。
129. ......
    struct horse My_horses[50];
     ......
     ......
    My_horses[1] = My_horses[2];//将My_horse[2]的所有成员复制到结构//My_horses[1]中,使这两个结构完全相同
使用整个结构的另一个操作是使用&运算符提取地址,但是不能对整个地址执行加、比较或其他操作。
130.声明结构的指针:
struct horse *phorse;//声明了一个phorse指针,它可以存储horse类型的结构地址
phorse = &My_horses[1];//phorse指向结构My_horses[1],通过phorse指针引用这个结构的元素
printf(" The name is %s.",(*phorse).name);//显示结构成员的名字
等价于
printf(" The name is %s.",phorse->name);//“->”成员指针运算符
131.要为结构动态分配内存,可以使用结构指针数组,声明如下:
struct horse *phorse[50];//声明了50个指向horse结构的指针数组。该语句只给指针分配了内存,还需分配一些内存来存储每个结构的成员。
132.malloc向系统申请分配指定size个字节的内存空间。返回类型是void*类型。void*表示未确定类型的指针。C,C++规定,void*类型可以通过类型转换强制转换为任何其它类型的指针。
       如果分配成功则返回指向被分配内存的指针(此存储区中的初始值不确定),否则返回空指针NULL。当内存不再使用时,应使用free()函数将内存块释放。函数返回的指针一定要适当对齐,使其可以用于任何数据对象。
与new的区别:
    从本质上来说,malloc是libc里面实现的一个函数,new是c++的关键字,它本身不是函数。new不依赖于头文件,c++编译器就可以把new编译成目标代码。   

在使用上,malloc 和 new 至少有两个不同:
  [1].new 返回指定类型的指针,并且可以自动计算所需要大小。
  int *p;
  p = new int;
//返回类型为int *类型(整数型指针),分配大小为sizeof(int);
或:
int *parr;
parr = new int[100];
//返回类型为int *类型(整数型指针),分配大小为sizeof(int) * 100;
  [2].而 malloc 则必须要由我们计算字节数,并且在返回后强行转换为实际类型的指针。
int *p;
p = (int*)malloc(sizeof(int) * 128);
//分配128个(可根据实际需要替换该数值)整型存储单元,
//并将这128个连续的整型存储单元的首地址存储到指针变量p中
double *pd = (double*)malloc(sizeof(double) * 12);
//分配12个double型存储单元,
//并将首地址存储到指针变量pd中
注:(1)malloc 函数返回的是 void * 类型。
         对于C++,如果你写成:p = malloc (sizeof(int)); 则程序无法通过编译,报错:“不能将 void* 赋值给 int * 类型变量”。所以必须通过 (int *) 来将强制转换。而对于C,没有这个要求,但为了使C程序更方便的移植到C++中来,建议养成强制转换的习惯。
       (2)函数的实参为 sizeof(int) ,用于指明一个整型数据需要的大小。
       (3)“判断内存是否已经分配好”举例:
               current=(struct horse *)malloc(sizeof(struct horse));
               if(current==NULL)
               {
                      printf(" Not enough memory.Hasta laVista,baby. ");
                      return 1;
               }
       (4)free(previous);//释放内存,不是说该地址的地址值为0,而是该地址存放的值被清空
133.使用sizeof运算符可以计算出结构所占的字节数,其结果不一定对应于结构中各个成员所占的字节数总和,如果自己计算,就很容易出错。
134.除了char类型的变量之外,2字节变量的起始地址常常是2的倍数,4字节变量的起始地址常常是4的倍数,以此类推。这称为边界调整。它和C语言无关,而是硬件的要求,以这种方式在内存中存储变量,可以更快地在处理器和内存之间传递数据,但不同类型的成员变量之间会有未使用的字节。这些未使用的字节也必须算在结构的字节数中。
135.可以将第一个结构用作第二个结构的成员,再将第二个结构作为第三个结构的成员,依此类推。但C编译器只允许结构最多有15层。如果结构有这么多层,则引用最底层的成员时,需要输入所有的结构成员名称。P423页
136.链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
    相比于线性表顺序结构,操作复杂。
    链表最明显的好处是,常规数组排列关联项目的方式可能不同于这些数据项目在记忆体或磁盘上顺序,数据的存取往往要在不同的排列顺序中转换。链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。
     链表有许多种不同的类型:单向链表,双向链表以及循环链表。
    见P427页“链表”实例分析,结合单步调试理解链表
    利用结构成员指针生成一个结构链,在这个链中,每个结构的next成员都指向下一个结构,最后指向的结构中的next指针设定成NULL,这称为链表。
    在需要处理数量未知的结构的应用程序中,链表非常有用,链表的主要优点是内存的使用和便于处理。存储和处理链表所占用的内存量最少,即使所使用的内存比较分散,也可以从一个结构进入下一个结构。因此,链表可以用于同时处理几个不同类型的对象,每个对象都可以用它自己的链表来处理,以优化内存的使用。但是链表的数据处理的速度比较慢,尤其是要随机访问数据时,速度更慢。
137.双向链表:在单向链表的基础上,除了指向下一个结构的指针外,在每个结构中再添加一个指针,存储前一个结构的地址。
    从而可以双向遍历链表。
    见P428“双向链表”的实例分析
138.结构中的“位字段”:它提供的机制允许定义变量来表示一个整数中的一个或多个位,这样就不需要为每个位明确指定成员的名称了。
    位字段常用在必须节省内存的情况下。很少用。
139.对于scanf()而言,%c是个较为特殊的说明符。%c前没空格,scanf()将读取标准输入流中的第一个字符,%c前有空格,scanf()则读取标准输入流中第一个非空白字符。
       第一次scanf()读取了一组数据后,把" "留在了标准输入流,第二个scanf()就从输入流中获取换行符了(读取数据已经结束,不再接收输入缓冲区内的数据)由于scanf()函数的特性,空白符直接忽略,因此程序便结束了,第二个scanf()函数看似失效,实际上是执行完毕了。
       见P427页“链表”实例的编程调试结果,“scanf加空格的问题”的word文档说明,以及P380页关于scanf函数的分析。
140.将结构指针作为函数变元:
(1)bool siblings(struct family const *pmember1,struct family const *pmember2)
   {
     ......
   }
注:按值传递机制禁止在被调用的函数中意外地更改变元值。因此加入const修饰符,将变量变成常量,此处这个函数声明将参数类型指定为family结构的常量指针。即不能够更改指针变元的值(只是访问并使用它们),把指针传送给函数还是可以获得某种程度的保护。
    传递给函数的结构指针在函数中被视为常量,试图改变结构,会在编译期间产生错误信息。
(2)bool siblings(struct family *const pmember1,struct family *const pmember2)
   {
     ......
   }
注:这里的参数是“指向family结构类型的指针常量”,因此可以在函数中随意改变结构,但是不能改变存储在指针内的地址。这里保护的是指针,而不是指针指向的结构。

    见P433页“结构指针作为函数变元”相关内容(存有疑问,尚待斟酌!!)
141.二叉树是组织数据的一种非常有效的方式,二叉树中的数据可以以有序的方式安排。
       实现二叉树涉及到递归和动态分配内存,还要使用指针传送结构。

递归:程序调用自身的编程技巧称为递归( recursion)。
           动态分配内存:(Dynamic Memory Allocation)就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。
           动态内存分配不像数组等静态内存分配方法那样需要预先分配存储空间,而是由系统根据程序的需要即时分配,且分配的大小就是程序要求的大小。
     二叉树包含一系列相互关联的元素,称为节点。起始节点是树的根,称为根节点。
     每个节点一般包含一个数据项,以及两个指向后续节点(左节点和右节点)的指针。如果有一个后续节点不存在,对应的指针就是NULL。节点还可以包含一个计数器,记录树中何时有重复的数据项。
     关于“二叉树”的具体内容分析(构建二叉树、遍历二叉树、用二叉树排序),见P441页
142.共享内存的3种情况:
(1)使用几个变量时,但其中只有一个变量在任意给定的时刻都有有效值;
(2)程序处理许多不同类型的数据,但是一次只能处理一种,要处理的类型在执行期间确定;
(3)要在不同的时间访问相同的数据,但是在不同的情况下,该数据的类型是不同的。
联合:C语言中允许多个不同变量共享同一内存区的功能。
匿名联合:声明一个结构类型,其中包含一个没有标记符名称的联合,这个联合的实例只能存在于结构的实例之中,这种联合常常称为匿名联合。
如下:struct my_structure
          {
               int num1;
               float num2;
               union
               {
                     int *pnum;
                     float *pfnum;
               } my_U;
          } samples[5];//定义了5个结构实例的数组samples。结构中的联合由两个指针共享,使用联合成员,其表示法和嵌套的结构相同                         

//例如,要访问结构数组中第三个元素的int指针,可使用如下语句:
//samples[2].my_U.pnum = &my_num;
143.类型定义(typedef):
      用关键字typedef声明这个结构,如下:
      typedef struct pts Point;//名称Point是struct pts的同义字声明pts结构的一些实例时,可用:
      Point start_pt;
      Point end_pt;//声明了两个结构变量

可以组合typedef和结构声明:
typedef struct pts
{
    int x;
    int y;
    int z;
}Point;//Point是类型的名称,不是结构变量名称
声明结构变量时,可使用:Point my_pt;
144.使用typedef定义结构指针pts,就可以定义一个类型:
    typedef struct pts *pPoint;
    声明某些指针时,可为:pPoint pfirst;
                                            pPoint plast;//两变量均声明pts类型的结构指针
    声明函数指针:
    typedef int(*function_pointer)(int,int);
//这不是声明函数指针的变量,而是将function_pointer声明为类型名称,以用来声明函数指针。如下:
    function_pointer pfun;
    也可以初始化它们,假设有函数sum()、difference()、product(),可以声明并初始化它们,如下:
    function_pointer pfun1 = sum;   
    function_pointer pfun2 = difference;
    function_pointer pfun3 = product;
这里定义的类型名称只能应用于在typedef语句中指定的那种类型的函数。如果要应用于其他函数,就必须定义另一个类型。
145.利用一组数据生成柱状图。见P456页
146.用于存储和检索数据的外部数据一般是固定磁盘。但不仅仅是固定磁盘,而C语言中用于处理文件的库函数都独立于设备,所以它们可以应用到任何外部存储设备上。
147.将数据存储到一个即使关掉计算机后,数据也不会消失的存储设备中。该存储设备就叫做文件,文件通常存储到硬盘上。
       文件其实是一系列的字节。文件有开头和结尾,还有一个当前位置,通常定义为从文件头到当前位置有多少个字节数。当前位置就是发生文件操作(读写文件的动作)的地方。
       当前位置可以移动到文件的其他地方去。新的当前位置可以指定为距离文件开头的偏移量,或在某些情况下,指定为从前一个当前位置算起的正或负偏移量
148.C库提供了读写数据流的函数,流是外部数据源或数据目的地的抽象表示,所以键盘、显示器上的命令行和磁盘文件都是流。可以使用输入输出函数读写映射为流的任意外部设备。
149.将数据写入流(即磁盘文件)有两种方式:
(1)将数据写入文本文件,
此时数据写入为字符,这些字符组织为数据行,每一行都用换行符结束。二进制数据,比如int或double类型的值,必须先转换为字符,才能写入文本文件。printf()函数可完成该格式化;
(2)将数据写入二进制文件,写入二进制文件的数据总是写入为一系列字节,与它在内存中的表示形式相同,所以double类型的值就写入为8个字节,与其内存表示形式相同。
         当然,可以将任意数据写入文件,一旦写入,数据最终都是一系列字节。即读取文件时,程序必须知道这个文件包含什么种类的数据。一系列字节代表的意义完全取决于我们怎么解释它们。
150.C语言中处理文件时,程序通过文件指针来引用文件。文件指针是一个抽象指针,关联到一个特定的文件上,所以程序可以在不同的情况下处理不同的文件。文件指针指向表示流的结构。
       打开文件:将内部文件指针变量关联到一个特定的外部文件名称上的过程。
调用标准库函数fopen()打开文件,返回特定外部文件的文件指针
在<stdio.h>中定义:
原型:FILE *fopen(char *name,char *mode)
    函数第一个变元是一个字符串指针,要处理的外部文件名称。也可以使用数组或一个char类型变量指针,包含了文件名字符串的地址。
    函数第二个变元是一个字符串,称为文件模式。指定对文件进行什么处理
     如果成功调用fopen(),则返回一个FILE*类型的指针,通过该指针可以引用文件,使用其他库函数执行进一步的输入输出操作。如果文件打不开,则返回一个空指针。
fopen()函数返回指针称为文件指针(file pointer)或流指针(stream pointer)
    文件指针指向的结构包含文件的信息,如指定的打开模式、内存中用于数据的缓冲区地址和指向文件中当前位置的指针,以用于下一个操作。
    要同时打开几个文件时,必须为每个文件声明各自的文件指针变量,并分别调用fopen()函数,将其返回值存储在各自的文件指针中。一次能打开的文件数由<stdio.h>中定义的常量FOPEN_MAX确定。C语言中,其值至少为8,包括stdin、stdout和stderr。因此,至少一次可以处理5个文件。
举例:如果要写入文本文件myfile.txt,可用下列语句:
FILE *pfile = fopen("myfile.txt","w");
//打开文件,将物理文件myfile.txt关联到内部的文件指针pfile上,且只能写  入文件,不能读取文件
注:如果文本myfile.txt不存在,则fopen()函数将在当前目录下创建它,也可以指定一个包含完整路径和文件名的字符串。如果目录也不存在,则调用失败,返回NULL;
pfile = fopen("myfile.txt","a");
//追加模式中打开文件时,所有的写入操作都在文件的数据末尾执行。即所有的写入操作都会把数据追加到文件中,不能更新已有的内容;
pfile = fopen("myfile.txt","r");
//读取文件,不能写入文件。文件位置设定在文件中数据的开头。如果要读入文件,文件就

声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。