《C和指针(Pointer on c)》 学习笔记

转载:http://dsqiu.iteye.com/blog/1687944html

首先本文是对参考中三个链接的博客进行的整理,很是感谢三位博主的努力,每次都感叹网友的力量实在太强大了……git

 

第一章 快速上手
1.  在C语言中用/*和*/来注释掉这段代码,这个实际上并非十分的安全,要从逻辑上删除一段C代码,最好的办法是使用#if指令:
    #if 0
        Statement
    #endif
2.  其余语言中,无返回值的函数称为过程(procedure)。
3.  数组作参数的时候是以引用(reference)的方式传递的,即地址传递。而标量和常量都是传值调用,被调用的函数没法修改调用函数以传值形式传递给它的参数,然而当被调用函数修改数组参数的其中一个元素时,调用函数所传递的数组就会被实际修改。
4.  字符是以一串NUL字节结尾的字符。NUL做为字符串终止符,它自己并不被看做字符的一部分,NUL表示字符串结尾,NULL表示空指针。

5.当传递一个数组时,能够无需指定数组的长度(若是须要长度,则须要再增长一个长度的参数)。算法

int  read(int a[], int len); express

(在函数内部最好进行出错检查)编程

6.使用scanf函数应该注意:使用全部格式码(除了%c以外)时,输入值以前的空白(空格、制表符、换行符等)会被跳过,值后面的空白表示该值的结束,所以,用%s格式码输入字符串时,中间不能包含空白。数组

7.编译器一般不对数组下标的有效性进行检查。缓存

8.注释是不安全的,是不容许嵌套的,老是与第一个*/相结合。安全

9.数据结构

 

int ch;异步

while((ch = getchar()) != EOF && ch != '\n'); 

ch被声明为整型,可是又用来读取字符的缘由:

EOF是一个整型数值,它的位数比字符类型要多,把ch声明为整型能够防止从输入读取的字符意外的解释为EOF,可是同时意味着接收字符的ch必须足够大,足以容纳EOF。 

 
第二章 基本概念
1.C语言在实现的过程当中,存在两种环境,一种是翻译环境(源代码被转换为可执行的机器指令),另外一种是执行环境(用于执行实际代码),这两种环境没必要位于同一台机器上,例如交叉编译。

2.翻译包括两个阶段:编译与连接,其中编译包括:预处理,解析,优化(可选)。

3.执行包括几个阶段:首先,程序必须再入到内存中。在宿主环境中(也就是具备操做系统的环境),这个任务由操做系统完成。那些不是存储在堆栈中的还没有初始化的变量将在这个时候获得初始值。而后,便开始执行程序代码。在绝大多数机器里,程序将使用一个运行时堆栈(stack),它用于存储函数的局部变量和返回地址。程序同时也可使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程当中将一直保留它们的值。

环境:翻译环境: 源代码转化成可执行的机器指令

执行环境:用于实际执行代码

翻译:源文件-〉目标文件-〉可执行文件(经过连接器将多个目标文件捆绑在一块儿)

编译过程:预处理器-〉源代码通过解析产生目标代码(这个过程当中是绝大多数错误和警告产生的地方)-〉优化器(就是对目标代码进行进一步优化,使效率更高)

执行:首先,程序被加载到内存,那些不是存储在栈中的未被初始化的变量将在这个时候被初始化;而后,程序的执行便开始了,负责处理一些平常事务,如收集命名行参数以便使程序可以访问他们,并开始调用main函数;如今,开始执行程序代码,在绝大多数机器里,程序将使用一个运行时堆栈,用于存储函数的局部变量和返回地址。程序同时也使用静态内存,存储与静态内存中的变量在整个程序执行过程当中将一直保持不变。最后,程序终止,正常终止的话,是main函数返回。

4.  三字母词:几个字符组合起来表示另一个字符
    ??(    [        ??<    {        ??=    #
    ??)    ]        ??>    }        ??/    \
    ??!    |        ??'    ^        ??-    ~
    print("You sure??!");    -→    You sure|
    转义字符:'\'(反斜杠)加上一个或者多个字符组成。

5.标识符是由大小写字母、数字、下划线组成,可是不能以数字开头的,而且不能采用关键字来做为标识符。

6.推荐的良好的程序风格(不必定要同样,只是为了程序的阅读和维护)

(1)空行用于分隔不一样的逻辑代码段,按照功能进行分段;

(2)在括号和表达式之间留下一个空格,可使表达式更加突出;

(3)在绝大部分操做符的使用中,中间都隔以空格;

(4)嵌套于其它语句的语句将缩进;

(5)尽可能将注释成块出现;

(6)在函数的定义中,返回类型出现于独立的一行中,函数的名字在下一行的起始处。

7.把一个大型程序放入一个单一的源文件中的优缺点:

    优势:容易找到修改函数的所在,连接所须要的时间会少点;

    缺点:不易阅读,不易维护,不利于多人合做。

 

第三章 数据
1.  变量三属性:做用域(Scope),连接属性(linkage),存储类型(Storage Class)。
2.  一、C语言中仅有4种基本数据类型:整型、浮点型、指针、聚合类型(数组和结构等)。

变量的最小范围:

char      0-127

signed char        -127-127

unsigned char       0-255

short int      -32767-32767

unsigned short int      0-65535

int      -32767-32767

    unsigned  int     0-65535
    整形数字以后添加字符L或者l,表示整数为long type;添加字符U或u,表示为unsigned type。长整型至少和整型同样长,整型至少和短整型同样长。可移植性问题:把存储与char变量的值,限制在signed char 和unsigned char 二者的交集之中,这样能够得到最大程度的可移植性,又不牺牲效率。而且只有当char 类型显示声明为signed 或 unsigned 时,才对它执行算术运算。使用字符常量所产生的都是正确的值,因此它能提升程序的可移植性。
3.  若是一个多字节字符常量前面有一个L,表示是宽字符常量(wide character literal)。如L'X', L'love',当运行环境支持宽字符集时,就可使用它们。
4.  8进制在表示的时候须要前面加一个0,如067;C/C++不容许反斜杠加10进制数字表示字符,因此8进制数表示的时候,能够省去零,如\67。
5.  枚举类型的值,实际上为数字;能够对枚举的符号名显式的指定一个值,后面枚举变量的值在此值基础上加1。
6.  浮点数default类型为double,后面跟L或l时是long double type,跟F或f时是float type。
7.  指针能够高效的实现tree和list数据结构。
8.  char *msg = "Hello World!";等价于:
    char *msg;  msg = "Hello World!";
    第一种看似赋值给了*msg,但本质上是赋给了msg。
9.  typedef最重要的用途是定义struct。
10. int const *pa;指向整型常量的pointer,能够修改pointer value,但不能够修改它所指向的value。
    int *const pb;指向整型的常量pointer,没法修改pointer value,但能够修改它所指向整型value。
    int const *const pc;pointer vale和指向的整型的value都不可被修改。
    const修饰的对象不变,上例前两个为:*pa和pb,也就是说*pa和pb的内容不变。
11. #define MAX 50
    int const max = 50;
    这种情况下,#define更合适,它的使用范围没有被限定;而const变量只能被用于使用变量的地方。建立新的类型名的时候,应该使用typedef而不是#define,由于后者没法正确处理指针类型。

 

Cpp代码  
  1. #define d_ptr_to_char char *  
  2. d_ptr_to_char a, b;  

这样a是正确声明了,可是b却声明为了字符类型。

12. Internal连接属性的标识符在同一源文件内的全部声明中都指向同一实体。
    External连接属性的标识符不论声明多少次,位于几个源文件都表示同一实体。
    具备external连接属性的标识符,前面加上static关键字能够是它的连接属性变为internal,static只对default属性为external的声明有效果。
13. Static:当用于函数定义时,或用于代码块以外的变量声明时,连接属性从external变为internal,标识符的存储类型和scope不受影响,只能在源文件中访问;当用于代码块内部的变量声明时,static关键字用于修改变量的存储类型,变动为静态变量,变量的连接属性和做用域不受影响。
    Type    声明位置          Stack    Scope             若是声明为static
    全局    全部代码块以外    否       声明处到文件尾    不容许从其余源文件访问
    局部    代码块起始处      是       整个代码块        不在stack中,value在程序运行中一直保持
    形参    函数头部          是       整个函数          不容许

 

14.存储变量的三个地方:普通内存,运行时堆栈,硬件寄存器(变量存储类型是指存储变量值的内存类型)。

变量的存储类型取决于它们声明的位置:

(1)凡是在任何代码块以外声明的变量老是存储在静态内存中(程序运行前建立,而且若是没显式赋值则建立时缺省的赋于一个初始值0,在程序执行完毕后销毁);

(2)在代码块内部声明的变量是存储在堆栈中(在程序执行到声明自动变量的代码块时建立,在离开这个代码块时销毁,若是加上关键字static能够将其修改成静态变量,可是不改变该变量的做用域,仅仅改变其存储类型)。

 

第四章 语句

1.  C没有专门的赋值语句,而是采用表达式语句代替。
2.  C没有bool类型,而是用整数类型代替,零为假,非零为真。
3.  跳出多层loop的方法:
    A. 使用goto语句。
    B. 设置status flag,在每一个循环中都去判断status flag。
    C. 把全部loop放在一个单独的函数中,使用return语句跳出loop。

else语句从属于最靠近它的不完整的if语句。

4.break和continue

   break用于永久终止循环,continue用于终止当前循环,这两条语句出如今嵌套的循环内部,它只对最内层的循环起做用。

5.当循环体为空的时候,单独用一行来表示一条空语句是比较好的作法,这样让人一目了然。

6.for语句与while语句执行过程的区别在于出现continue语句时,在for语句中,continue语句跳过循环体的剩余语句,直接回到调整部分(即for语句的第三个表达式);在while语句中调整部分是循环体的一部分,因此continue将会把它也跳过。

7.在while语句和do语句之间的选择:当须要循环体至少执行一次时,选择do语句。

8.对于switch语句:

switch(expression)

statement;

expression的结果必须是整型值。

switch语句执行时是贯穿全部的case标签。

9.switch语句的case标签只是决定语句列表的进入点,而不是划分它们,要划分它们,则须要break语句的帮助。

10.最好在switch语句中的最后一个case语句也加上一个break,这个有利于将来的维护,避免错误。

11.每一个switch语句只能出现一条default子句,最好在每一个switch语句中就加上一条default子句,这样能够避免一些没必要要的错误。

12.C语言不具有任何输入/输出语句,I/O是经过调用库函数来实现的,也不具有异常处理语句,也是经过调用库函数来完成的。
 
第五章 操做符和表达式
1.  移位计数
    int count_one_bits(unsigned value)
    {
        int ones;
        for( ones = 0; value != 0; value = value>>1)
        {
            If(value%2 != 0)
            ones = ones + 1;
        }
        return ones;
    }
    更有效率的方法:
    int counter(unsigned value)
    {
        int counter = 0;
        while(value)
        {
            counter++;
            x = x&(x-1);
        }
        return counter;
    }
    此外x&(x-1)还能够快速断定x是否为2^n。
2.  位操做符
    指定bit置1: value = value | 1<<bit_number
    指定bit清0: value = value & ~(1<<bit_number)
    检测指定bit: value & 1<<bit_number
3.  sizeof():判断操做数类型长度,以byte为单位;操做数既可使表达式,也能够是类型名。sizeof后若是是类型必须加括弧,若是是变量名能够不加括弧。这是由于sizeof是个操做符不是个函数。若是是指针变量则返回4(指针变量自己存储大小)。
    sizeof()的操做数为数组名时,返回的是该数组的长度。联合类型操做数的sizeof是其最大字节成员的字节数。结构类型操做数的sizeof是这种类型对象的总字节数,包括任何垫补在内。判断表达式的长度并不会对表达式求值。sizeof 操做符不能返回动态地被分派了的数组或外部的数组的尺寸 。
4.  短路求值(short-circuited evaluation):"&&","||","?:",","
    (L→R)左操做数的结果决定是否对表达式进一步求值
5.  利用'.'和'->'访问结构体的区别:
    s为struct变量时候,使用s.a访问s中的成员a;
    s为指向struct变量的指针时,使用s->a访问成员a。
7.  左值:表示一个存储位置,能够出如今赋值符的左边或右边;
    右值:表示一个value,只能出如今赋值符的右边。
8.  算术转换(arithmetic conversion):操做数类型的转换。(类型的提高)
    经常使用方法是在执行运算以前把其中一个(或多个)操做数类型转换为左值的类型。
9.  复杂表达式的求值顺序3要素:操做符的优先级,操做符的结合性,操做符是否控制执行顺序(短路求值)。
    优先级只对相邻操做符的执行顺序有效。
    非法表达式的求值顺序(规范没有定义)由编译器决定。

        若是顺序会对致使结果产生区别,最好使用临时变量

10.C语言的算术操做符中只有%操做符要求两边都必须是整型数,其余操做符都是既适用于浮点类型又适用于整数类型。

11.C语言中的左移操做时,右边空出来的位均用0补齐,然而右移操做分为两类:逻辑右移和算术右移。    逻辑右移是左边移入的位用0填充,算术移位是左边移入的位由原先该值的符号位决定的,符号位为1的移入的位均为1,符号位为0的移入的位均为0。

注:无符号值执行的全部移位操做就是逻辑移位,而有符号值的移位操做是取决于编译器;所以若是一个程序若是使用了有符号数的右移移位操做,它就是不可移植的。应该避免下面类型的移位操做,这个移位的位数也是由编译器决定的。

12.C语言中位操做符要求操做数必须是整数类型;

13.关于位操做中的经常使用操做:

(1)置位:

value  = value | 1 << bit_number;  

(2)清零:

value = value & ~(1 << bit_number);  

(3)测试:

value & 1 << bit_number;  

14.赋值操做符的结合性是从右到左(求值的顺序),在使用赋值运算时应该考虑截短问题的存在;

15.使用复合赋值符可以使源代码更加容易阅读和书写;

16.sizeof是单目操做符而不是一个函数;

17.关于++操做符和--操做符的理解

   关于前缀形式的++操做符和后缀形式的++操做符,在操做数以前的操做符在变量值被使用以前增长它的值,在操做数以后的操做符在变量值被使用后才增长它的值;对于前缀和后缀形式的增值操做符都复制一份变量值的拷贝,用于周围表达式的值正是这份拷贝,所以这些操做符的结果不是被它们所修改的变量,而是变量值的拷贝,故它们不能像下面所示的形式进行操做:

a++ = 10; ++a = 10;  

(a++和++a的结果都是a的拷贝,并非变量的自己,所以没法进行赋值)。

18.条件操做符可以产生更小的目标代码,从而提升效率。

19.逗号操做符可以使代码更加易于维护:

 

逗号运算符 (,)

顺序执行两个表达式

expression1, expression2

说明

, 运算符使它两边的表达式以从左到右的顺序被执行,并得到右边表达式的值。 逗号运算符的优先级别在全部运算符中最低。, 运算符最普通的用途是在 for 循环的递增表达式中使用。例如:

for (i = 0; i < 10; i++, j++)

{

   k = i + j;

}

每次经过循环的末端时, for 语句只容许单个表达式被执行。, 运算符被用来容许多个表达式被看成单个表达式,从而规避该限制。

再以下面实例:

   int x,y,z; 

x=y=1; 
z=x++,y++,++y; 
printf("%d,%d,%d\n",x,y,z); 

上面的表达式中应该等价于这样的结合:(z=x++),y++,++y;若是这样写的话,则答案很清晰,为:2,3,1。

例题1:(a = 3,b = 5,b+ = a,c = b* 5),求逗号表达式的值?

   答案:40。前两个表达式只是赋值,从第三个开始计算,b+=a,即b=b+a,即b=5+3,b=8,求最后一个表达式,c=b*5=8*5=40.由于逗号表达式的值是最后一个表达式的值,因此整个逗号表达式的值为40,其余各变量最后的值依次为:a=3,b=8,c=40。

  例题2:若已定义x和y为double类型,则表达式:x=1,y=x+3/2的值是

  A) 1 B) 2 C) 2.0 D) 2.5

 

  分析:该表达式是一个逗号表达式,因此先运算x=1,结果变量x中的值为1.0,而后运算y=x+3/2,其结果是变量y中的值为2.0(这个运算过程可参阅本专题的“整数除法的注意事项”——整数相除,舍入法取整数部分),注意此时表达式y=x+3/2的值即等于变量y的值为2.0。最后,整个逗号表达式的值应该等于最后一个表达式的值2.0,因此,正确答案是C)。 

  例题3:若t为double类型,表达式t=1,t+5,t++的值是

  A) 1 B) 6.0 C) 2.0 D) 1.0

  分析:该题的表达式也是一逗号表达式,运算过程同例题1。须要注意的是,其中的第二个表达式(t+5)对整个表达式的运算结果不产生任何影响,由于它没有改变变量x的值(x的值仍为1.0),最后一个表达式(t++)的值为变量x进行自增运算前的值1.0,因此整个表达式的值为1.0。

例题4:有以下函数调用语句
  func(rec1,rec2+rec3,(rec4,rec5));
 
  该函数调用语句中,含有的实参个数是3。 

 

 

20.C的整型算术运算老是至少以缺省整型类型的精度来进行的。

 
第六章 指针
1.  边界对齐:boundary alignment(内存对齐)。
2.  不能简单的经过检查一个值的位来判断它的类型。
    数据的意义不在于它的类型,而在于它被使用的方式。
3.  指针的初始化用&操做符来完成,它用于产生操做数的内存地址。
4.  声明的指针必须初始化才可使用,否则不能肯定指针指向的地方。
5.  安全策略:让函数返回独立的值。首先是函数返回的status value, 用于判断操做是否成功;其次是形参pointer,用于在操做成功时返回结果。
6.  指针变量能够作左值,是由于它们是变量(存储地址)。
7.  指针的强制类型转换:
    * 100 = 120; // 非法语句,由于间接访问表达式(*)只能做用于指针类型表达式。
    * (int *) 100 = 120; // 合法语句,把100从"整型"转换为"指向整型的指针"。(这仅仅是个例子)
8.  指针运算:
    指针 +/- 整数:适用于指向数组中某个元素的指针,且要保证不越界。
    指针 – 指针:适用于当两个指针都指向同一数组中的元素时。
    关系运算:<, <=, >, >=,适用于指向同一数组中的元素的两个指针,表示的意义为哪一个指针指向的数组元素更靠前或靠后。
    指针运算只有做用于数组中,其结果才是能够预测的。
9.引发段错误缘由是引用非法地址,总线错误缘由是数据在内存中的存储地址处在错误的边界上。

10.对一个NULL指针进行解引用的操做是非法的,在对指针进行解引用操做以前,首先必须确保它不是NULL指针。

11.&ch可以做为一个右值来使用,可是不能用来当左值来使用,至于缘由:咱们知道左值意味着位置,&操做符的的结果应该放在哪一个位置,确定位于一个地方,可是你没法知道在什么地方,所以这个表达式并未标识内存的位置,因此没法做为一个合法的左值。

 由此可知,做为一个左值,必需要有对它的存储位置有个清晰的定义(关于左值,右值在上一章有提到)。

12.在函数内,若是传进来一个指针,应该对其进行检查是不是NULL指针;若是两个指针所指向的不是同一个数组中的元素,那么它们之间相减的结果是未定义的。

13.注意:

1. 能够把指针初始化为0、NULL或某个地址,具备值NULL的指针不指向任何值。

2. 当把0赋值给指针时,编译器先把0转换为指向合适数据类型的指针。

3. 值0是惟一可以直接赋给指针变量的整数值。

4. 切忌使用未初始化的指针会给系统带来隐藏的危害,一但指针指向非法区域,会形成系统崩溃。

 

第七章 函数
1.  C函数的实现运用了堆栈。
    函数在调用的时候,要为被调用的函数分配内存空间(堆栈),相关寄存器的值也必须保存;在函数返回以后,释放内存空间,恢复相关寄存器的原始值。
2.  K&R C 中,形参的类型是以单独列表的形式声明的。
    int *  Find_int ( key,  array,  array_len )  int key;  int array[];  int arrary_len;
    {
        // Function Body
    }
    这种声明形式,新标准仍兼容。
3.  函数能够分为:真函数(有返回值,默认类型为整型)和过程函数(无返回值)。
4.  函数在使用以前,必须进行"声明"。
5.  无参函数: int * func (void); // 关键字void 提示没有任何参数。
6.  C函数的全部参数均为传值调用,函数得到的参数值只是实参的拷贝。
    参数为指针时传递的值为指针对象的地址,这个行为被称为传址调用,也就是许多其余语言所实现的var参数。
    使用指针做为函数参数对结构进行传值,能够提升程序的运行效率。
    指针形参应尽量的声明为const,防止函数改变指针形参指向对象的值。
7.  C能够用于设计和实现抽象数据类型(ADT, abstract data type), 由于它能够限制函数和数据定义的做用域。(Black Box: 黑盒的功能经过规定的接口访问)
8.  static的合理使用能够限制对模块的访问,限制对那些非接口的函数和数据的访问。static声明的函数或数据,只能在文件内部被访问。
9.  许多问题是以递归的形式进行解释的,这只是由于它比非递归形式更为清晰;但迭代的方式每每比递归更有效率。(用递归的方法去作斐波那契数列是很是浪费资源的<二的几何次方>)
10. stdarg.h:实现了可变参数列表。
    #define va_start(ap, parmN)(ap = ...)
    #define va_arg(ap, type)(*((type *)(ap))++)
    #define va_end(ap)
    Stdarg.h中声明了类型va_list和三个宏:va_start, va_arg 和 va_end。经过在函数中声明va_list 变量,与三个宏配合使用,访问参数的值。此宏不能够判断参数的个数和类型。
    va_start(va_list, value);  // value是参数列表中省略号钱最后一个有名字的参数。初始化过程是把va_list 指向可变参数的第一个参数,因此第一个参数必须是一个有命名的参数。
    va_arg(va_list, type);  // type是va_list中下一个参数的type。    
    va_end(va_list);  // 访问完全部的参数以后,调用va_end结束。
    能够在访问参数的过程当中停止,但参数必须从第一个参数开始依次访问。全部做为可变参数传递给函数的值都会执行默认参数类型提高。
    因为参数列表中的可变参数部分没有原型(type),因此,全部可变参数传递给函数的时候都将执行缺省参数类型提高。(默认参数类型提高:default argument promotion,在参数产地给函数以前,char和short提高为int,float提高为double…..)
11.    递归函数的两个条件:(1).有限制条件(2).每次操做后愈来愈接近这个限制条件。

15.    递归函数运行设计一些开销:参数必须压到堆栈中,为局部变量分配内存空间,应该慎重使用递归的方法,可用迭代法代替。

16.当程序调用一个没法见到原型的函数时,编译器便认为该函数返回一个整型值。

17.函数传递的参数不能超过5个。

 

第八章 数组
1.  不可使用"="把一个数组的全部元素复制给另外一个数组。
2.  int  array[10];
    int  *ap = array + 2 ;
    C的下标引用和间接表达式是同样的。
    ap[0],这个表达式是彻底合法的,这种状况下对等的表达式为*(ap+(0))。
    2[array],这个表达式也是合法的,转换为间接表达:*(2+(array))。
3.  下标不会比指针更有效率,但指针有时会比下标更有效率。(效率:指针≥下标,如对数组进行循环赋值时。)当你根据某个固定的数目在一个数组中移动时,使用指针代码将比使用下标产生更加有效率的代码,当这个增量是1,而且机器具备地址自动增量模型时,这点表现更加突出。
4.  int  a[5];  // 初始化,分配了内存,*a彻底合法。
    int  *b;  // 未初始化,*b指向内存中不肯定的位置。
    声明数组时,同时分配了内存空间,用于存储数组元素;声明指针时,只分配了存储指针自己的内存空间,其指向的内存位置是未知的。
5.  函数形参中声明为指向const的指针:
    A. 有助与使用者仅观察该函数形参就能发现该数据不会被修改。
    B. 编译器能够捕捉任何试图修改该数据的意外错误。
    C. 这类声明容许函数传递const参数。
6.  char  message[] = "Hello"; // char数组初始化的一种方法,并不是字符串常量。
7.  int  matrix[3][10];
    ==> int  (*p)[10] = matrix; // 指针p指向matrix的第一行。
    ==> int  *pi = &matrix[0][0]; // 指针pi指向matrix第一行第一个元素。
    ==> int  *pt = matrix[0]; // 指针pt指向matrix的第一行。
    指向整型指针的指针(int  **p),和指向整型数组的指针是不等价的(int (*p)[10])。
    多维数组作函数参数时,必须显式的指明第二维和之后维度的长度。这是由于多维数组的每一个元素自己是另一个数组,编译器须要知道他的维数,以便为函数形参的下标表达式进行求值。
8.  char const keyword[] = {
    "do", "for", "if", "register", "return"};
    数组keyword的元素个数为:sizeof(keyword)/sizeof(keyword[0]);。
9.  指针数组:数组的成员为指针。声明方式:int *pt[]。 

10.     数组名是指向某种类型的指针常量,表示数组第一个元素的地址,数组名是一个常量是不能被修改的。

11.     声明为寄存器的指针比静态内存和堆栈中的指针效率更高。

12.     若是你能够经过测试一些已经初始化并通过调整的内容来判断循环是否终止,那么你就不须要一个单独的计数器。

13.     函数中的形式参数传递的是数组时,不指定数组大小的缘由是由于数组传递的时候是以指针的形式传递的。

14.     多位数组的名称表示的是,指向第一个数组的指针。

15.     max[3,4]等价于max[3]。

16.     多维数组中初始化时,记得要把花括号加上(更容易区分,还有就是能够给缺乏元素的初始化为0)。

17.    只要有可能函数的形式参数都应该声明为const。

18.只有两种场合下,数组名并不用指针常量来表示——就是当数组名做为sizeof操做符或单目操做符&的操做数时,sizeof返回整个数组的长度,而不是指向数组的指针的长度,取一个数组名的地址所产生的是一个指向数组的指针。

19.除优先级之外,间接引用与下标引用彻底同样;间接引用与下标引用能够进行相互转换。

20.数组与指针的区别:

(1)声明一个数组时,编译器将根据声明所指定的元素数量为数组保留内存空间,而后再建立数组名,它的值是一个常量,指向这段空间的起始位置;

(2)声明一个指针变量时,编译器只为指针自己保留内存空间,它并不为任何整型值分配内存空间,并且,指针变量并未被初始化为指向任何现有的内存空间,若是是一个自动变量,它根本就不会被初始化。

21.若是在程序的执行每次进入该函数(代码块)时,每次都对数组进行从新初始化不是什么有必要,就能够把数组声明为static,这样数组的初始化只需在程序开始前执行一次。

22.

char message[] = "hello";

char *message = "hello";

前者初始化一个字符数组的元素,然后者是一个真正的字符串常量,这个指针变量被初始化指向这个字符串常量的存储位置。

23.在C中,多维数组的元素存储顺序按照最右边的下标率先变化的原则,称为行主序。

24.二维数组名是一个指向数组的指针;

int matrix[3][10];

int *p = matrix;

int **p = matrix;

int (*p)[10] = maxtrix;

以上三种声明只有一个是合法的,首先咱们知道matrix是一个指向数组的指针,第一个p是一个指向整型的指针,而第二个是为指向整型指针的指针,只有三个是指向整型数组的指针,故第三个是合法的。

25.做为函数参数的多维数组名的传递方式和一维数组名相同——实际传递的是个指向数组第一个元素的指针,可是二者之间的区别是:多维数组的每一个元素自己是另一个数组,编译器须要知道它的维数,以便为函数形参的下标表达式进行求值。

26.数组形参既能够声明为数组,也能够声明为指针,这两种声明形式只有当它们做为函数的形参时才是相等的。
 
第九章 字符串、字符和字节
1.  strlen()返回的是无符号整型数。
    无符号数之间进行的操做,结果仍是无符号数。尽可能不要在表达式中使用无符号数(可能致使表达式的结果不可预料);如:strlen(x) – strlen(y),他的结果永远是大于等于0的。如要避免上述问题须要进行类型的强制转换。
2.  字符串以NUL结尾,可经过判断是否为NUL计算长度。
3.  复制: char  *strcpy( char *dst,  char const *src ); // strlen(src)<strlen(dst)时dst数据丢失,strlen(src)>strlen(dst)时dst数据溢出。
    链接: char  *strcat( char *dst,  char const *src );
    比较: int  strcmp( char const *s1,  char const *s2 ); // 相等返回0
    长度受限的字符串函数:
    char  *strncpy( char *dst,  char const *src,  size_t len ); // 若是strlen(src)小于len,dst数组会用NUL填充到长度len。要保证len小于等于strlen(dst).
    char  *strncat( char *dst,  char const *src,  size_t len );
    int  strncmp( char const *s1,  char const *s2,  size_t len );
4.  字符串查找:
    char  *strchr( char const *str,  int ch ); // return第一次出现的位置
    char  *strrchr( char const *str,  int ch ); // return最后一次出现的位置
    char  *strpbrk( char const *str,  char const *group ); // 查找一组字符中任一字符第一次出现的位置
    char  *strstr( char const *s1,  char const *s2 ); // 查找一个子串第一次出现的位置
    size_t  strspn( char const *str,  char const *group ); // 第一次匹配的相对位置
    size_t  strcspn( char const *str,  char const *group ); // 第一次不匹配的相对位置
    char  *strtok( char *str,  char const *group ); // 函数会修改所处理的字符串
    for( token = strtok(source, sep); token != NULL; token = strtok(NULL, sep)); // 若是strtok的第一个参数为NULL,函数会从同一个字符串中上一个保存的位置开始继续查找;若是没有结果,返回NULL指针。
5.  char  *strerror( int error_number ); // strerror把一个错误代码做为他的参数,返回一个指向字符串的指针,该字符串用于描述这个错误。
6.  字符操做函数: iscntrl, isspace, isdigit, isxdigit, islower, isupper, isalpha, isalnum, ispunct, isgraph, isprint;使用这些函数能够加强程序的可移植性。
7.  内存操做:如下函数遇到NULL字符不会中止
    void  *memcpy( void *dst,  void const *src,  size_t length );
    void  *memmove( void *dst,  void const *src,  size_t length );
    void  *memcmp( void const *a,  void const *b,  size_t length );
    void  *memchr( void const *a,  int ch,  size_t length );
    void  *memset( void *a,  int ch,  size_t length );
    memcpy的效率高于memmove,但memmove在dst和src存储位置发生重叠时,能够继续使用。

8.NUL字节是字符串的终止符,但它自己并非字符串的一部分,因此字符串的长度并不包括NUL字节。

9.用于复制字符串的strcpy原型:

char *strcpy(char *dst, char const *src);     

因为dst参数将进行修改,因此它必须是一个字符数组或者是一个指向动态分配内存的数组指针,不能使用字符串常量;使用这个函数时,必须保证目标字符数组的空间足以容纳须要复制的字符串。

10.strcpy与strcat都返回一个第一个参数的一份拷贝,就是一个指向目标字符数组的指针,所以能够嵌套调用这两个函数。

11.在使用strncpy函数时,最好按以下的方法:

strncpy(buffer, name, size);

buffer[size - 1] = '\0';

这样能够保证buffer中的字符串是以NUL结尾的;

12.strncat与strncpy不一样,strncat老是在结果字符串后面加上一个NUL字符。

 

第十章 结构和联合
1.    下标操做和点操做具备相同的优先级,都是从左到右进行操做。可是点操做符的优先级高于间接访问操做符。
2.  "–>"操做符的左操做数必须是一个指向结构的指针。
3.  结构体自引用结构体是非法的,能够经过引用指向结构体的指针解决。
4.  不彻底声明:先声明一个做为结构标签的标识符,而后能够把标签用在不须要知道这个结构长度的声明中(如指针)。
    struct B;
    struct A {
        struct B *pt;
    };
    Struct B {
        sturct A *pt;
    };
    在A的成员列表中须要标签B的不彻底声明,A声明以后,B的成员列表也能够被声明。
5.  内存对齐:编译器为一个结构变量分配内存空间时,需知足它们的内存对齐要求。(程序的可移植性)
    结构体的成员声明顺序会影响到结构体所需的存储空间。
    struct A {char a; int b; char c;};  struct B {char a; char b; int c;};
    在一个int长度为4B,而且其存储位置必须为4的整数倍的机器上,sizeof(A)的长度为12,sizeof(B)的长度为8。
    sizeof可以得出一个结构的总体长度,包括因内存对齐而跳过的字节。肯定结构成员的实际位置,可使用offsetof宏(stddef.h)。
    offsetof(type, member); // type是结构的类型,member是成员名。
10. 结构体指针作函数参数时,加上const用于禁止修改指针指向结构的数据。
11. 位段:可以把长度为奇数的数据封装在一块儿,节省存储空间;能够很方便的访问一个整型值(register)。(位段是结构的一种)
    位段成员必须声明为:int,signed int,unsigned int型,在成员名以后必须有一个冒号和一个整数,整数是该段所占用位的数目。
    struct RegisterA {unsigned aa:1; unsigned ab:2; unsigned ac:5;};
12. 联合union:成员储存在内存中的同一位置。联合变量的初始化必须是第一个成员的类型,并且必须在一对"{}"中。
    union {int a;  float b;  char c[4];}x = {5};
    union的一个重要的做用是多选一。好比人的性别是两种不一样的属性结构,在定义一我的的结构时就可使用联合来选择男女这两种属性结构

13.    若是你想在多个元文件中使用同一类型的数据结构,你应该把标签声明或typedef形式的声明放在一个头文件中。

14.     结构成员能够是标量,数组,指针甚至是其余结构。

15.     注意结构的不完整声明

    一、C提供两种类型的聚合数据类型,数组和结构,数组是相同类型元素的集合,而使用结构可以把不一样类型的值存储在一块儿;数组是经过下标引用或指针间接访问元素的,而结构能够经过成员名字来访问的,固然结构也能够经过指针来进行间接访问的。

二、不一样的结构声明即便它们的成员列表相同也被认为是不一样的类型:

struct {

int a;

int b;

float c;

}x;

 

struct {

int a;

int b;

float c;

}y[20],*z;

对于以上的描述,这两个声明被编译器看成两种大相径庭的类型,即便它们的成员列表彻底相同,所以变量y和z的类型和x的类型不一样,所以下面这条语句不成立:

 

z=&x;

16.做为函数参数的结构,若是采用传递结构,则须要复制整个结构的长度到堆栈中,再丢弃,这样的效率比较低,而传递结构指针比结构小得多,所以可以提升效率,可是向函数传递结构指针也有不足之处,就是能够对调用的变量进行修改(能够用const声明结构指针来防止)。

17.联合的声明与结构相似,联合的全部成员引用的是内存中相同的位置,可用于某一时刻,只有一个字段被使用,提升效率。

18.若是联合的各个成员具备不一样的长度,联合的长度就是它最长成员的长度。

19.联合变量能够被初始化,可是这个初始值必须是联合第一个成员的类型,并且它必须位于一对花括号里面。

例如:(把x.a初始化为5)

union{

int a;

int b;

char c[4];

}x={5};

   咱们不能把这个类量初始化为一个浮点值或字符值,若是给出的初始值是任何其余类型,它就会转换为一个整数赋给x.a。

 
第十一章 动态内存分配
1.  void  *malloc( size_t size ); // memory allocate
    void  free( void *pointer ); // free memory allocate
    void指针表示能够转换为任何其余类型的指针。
    free函数的参数能够是NULL,要么就必须是malloc、calloc、realloc返回的指针。释放一块内存空间是不容许的,动态分配的内存必须整块的释放;并且动态分配的内存必须在使用完以后被释放,否则会引发内存泄漏(memory leak)
2.  在使用函数分配的内存前,必须先确保返回的指针不为NULL。
3.  void  *calloc( size_t num_elements,  size_t element_size ); // 元素的数量和大小
    void  realloc( void *ptr,  size_t new_size ); 
    calloc函数在分配内存空间以后会在返回指针以前把内存空间初始化为0。
    realloc函数不会改变原内存中存储的数据。当用于扩大内存空间时,能够在原内存空间以后增长;当原内存空间没法修改时,realloc将会分配另一块符合要求的内存空间,并把原内存空间的内容复制到新内存空间上。所以,在使用realloc以后,原有内存空间的指针不能继续使用,应使用realloc返回的指针。realloc用于修改一个原先已经分配的内存块的大小,在使用realloc以后,就不能再使用指向旧内存块的指针,而是应该使用realloc返回的新指针,若是realloc的第一个参数是NULL,那么它就跟malloc同样;
4.  a > b ? 1 : ( a < b ? -1 : 0); // 三种结果均可以兼顾到。
5.  动态内存分配容许程序为一个长度在运行时才知道的数组分配内存空间。

6.       malloc 函数用来实现从内存中提取一块合适的内存,并向该程序返回一个指向这个内存的指针。这块内存如今并无进行任何初始化。当一块之前使用的内存没有使用的时候,程序调用free函数将它归还给内存池供之后使用。

         malloc函数当没有内存能够分配时,就会返回一个null指针,因此对null指针的判断很重要。

         malloc和calloc的区别:一是,后者在返回指针以前先把内存初始化为0;二是,calloc包括须要元素的数量和每一个元素的字节数,根据这个值,它能计算出到底须要分配多少内存。

         realloc用于修改原先已经分配好的内存大小。能够扩大也能够缩小,扩大时,前面的存储内容不变,后面的不被初始化;缩小时,尾部的内存便被砍掉。若是原先的内存不能改变大小,该函数将会从新分配一块新的内存,将原先的内存中的内容复制过来。所以使用realloc 后就不能在使用指向旧内存的指针了,而是应该使用realloc 返回的的新指针。

7.       若是偶尔调用了malloc,程序将因为语法错误而没法编译,在alloc中必须加入#undef指令,这样他才能调用malloc而不至于出现语法错误。

8.       不要使用已经被释放的内存。

9.       分配的内存在使用完毕后不进行释放将会产生内存泄露。一个持续分配却一点都不是放内存的程序最终将耗尽可用的内存。

10. 数组在声明时,它所须要的内存在编译时候就被分配;数组有其优势与缺点:优势在因而简单,缺点是(1)没法预知长度,数组没法处理程序所须要使用的元素

11.动态分配内存的常见错误:

(1)对NULL指针进行解引用,即忘记检查所请求的内存是否成功分配;

(2)操做内存时超出了分配内存的边界;

(3)试图释放一块动态分配的内存的一部分,所以传递给free的指针必须是一个从malloc、calloc、realloc函数返回的指针;

(4)一块动态内存被释放后被继续使用;

(5)内存泄漏,所以应保证内存再也不使用时,释放内存。

 

第十二章 使用结构和指针
1.  链表的存储空间是动态分配的。
2.  单链表是一种使用指针来存储值的数据结构。
3.  双向链表:一个指针指向前一个链表节点,另外一个指针指向后一个链表节点。
4.  语句的提炼用来简化程序,消除冗余的语句。

5. 在链表中,每一个节点包含一个链表下一个节点的指针,连表最后一个指针字段的值为null。为了记住链表的起始位置,可使用一个根指针(root pointer),跟指针指向链表的第一个节点。注意跟指针只是一个指针,它不包含任何数据。

6. 链表就是一些包含数据的独立数据结构(一般称为节点)的集合;链表中的节点可能分布于内存的各个地方,不必定是物理上相邻,单链表只能一个方向进行遍历。

7. 链表中若是你想遍历其余的节点,你只能从根节点开始。能够对链表进行排序。

8.语句提炼 :若是语句对if语句执行没有影响咱们能够将这个语句提早,若是语句在if执行后对这条语句没有影响,能够将这条语句放到后面。

9.不要仅仅以代码的大小衡量代码的效率。 
10.在对链表的操做过程当中,应该注意如下几点:

(1)动态分配内存时务必检查是否分配成功(检查返回值是否为NULL);

(2)操做的过程当中,应该避免对NULL指针进行解引用,因此对于有可能出现NULL指针的地方都要进行判断。

 

第十三章 高级指针话题
1.  int*  f, g; // 并无声明两个指针,星号只做用于f。
    int  *f(); // 函数操做高于间接访问操做符,因此函数返回的是一个整形指针。
    int  *f[]; // 下标优先级更高,因此f是一个数组,元素是整型指针。
    int  (*f)(); // f为一个函数指针
    int (*f[])(); //声明合法,数组f的元素是函数指针。
    函数和变量都必须先声明后使用,函数的类型能够认为是返回值的类型。    
2.  _cdecl:c declaration,表示默认方式:参数由右向左依次入栈,参数由调用者清除(手动清除)。
3.  函数指针的初始化:
    int  f(int);
    int  (*pf)(int) = &f;或者int (*pf)(int); pf=f;
    函数名在使用时总被编译器转换为函数指针,因此前一种初始化方式中的&操做符是可选的,&操做符只是显式的说明编译器将要执行的任务。
4.  回调函数:callback function,函数指针做为参数被调用时,指针指向的函数被称为回调函数。回调函数分离实现了调用函数和被调用函数,调用函数不用关心谁是被调用函数(函数指针,回调函数),只需知道有一个具备特定原型和限制条件的被调用函数。
    回调函数实现了调用函数的通用性,使得调用函数能够支持多种数据类型和多种逻辑状况。
    调用函数常把与回调函数相关的参数类型声明为void*,表示为指向未知类型的指针,加强了调用函数的泛用度。
5.   函数指针最多见的两个用途是转换表和做为参数传递给另外一个函数。转移表:实质上是一个函数指针数组,经过肯定数组中的元素来选择调用相应的函数。使用转移表时应该检验下标的有效性。
转换表最好用个例子来解释。下面的代码段取自一个程序,它用于实现一个袖珍式计算器。程序的其余部分已经读入两个数(op1和op2)和一个操做数(oper)。下面的代码对操做符进行测试,而后决定调用哪一个函数。
      switch( oper ){
      case ADD:
              result = add( op1, op2);
              break;
      case SUB:
              result = sub( op1, op2);
              break;
      case MUL:
              result = mul( op1, op2);
              break;
      case DIV:
              result = div( op1, op2);
              break;
        
        ......
      对于一个新奇的具备上百个操做符的计算器,这条switch语句将很是长。
      为何要调用函数来执行这些操做呢? 把具体操做和选择操做的代码分开是一种良好的设计方法,更为复杂的操做将确定以独立的函数来实现,由于它们的长度可能很长。但即便是简单的操做也可能具备反作用,例如保存一个常量值用于之后的操做。
      为了使用 switch 语句,表示操做符的代码必须是整数。若是它们是从零开始连续的整数,咱们可使用转换表来实现相同的任务。转换表就是一个函数指针数组。
      建立一个转换表须要两个步骤。首先,声明并初始化一个函数指针数组。惟一须要留心之处就是确保这些函数的原型出如今这个数组的声明以前。
      double add (double,double);
      double sub (double,double);
      double mul (double,double);
      double div (double,double);
      ......
      double ( *oper_func[] )( double, double)={
          add,sub,mul,div,...
      };
      初始化列表中各个函数名的正确顺序取决于程序中用于表示每一个操做符的整型代码。这个例子假定ADD是0 ,SUB是1,MUL是2,依次类推。
      第 2 个步骤是用下面这条语句替换前面整条 switch 语句!
      result = oper_func[ oper ]( op1,op2 );
      oper从数组中选择正确的函数指针,而函数调用操做符执行这个函数。
 
6.  命令行参数:int main( int argc,  char *argv[] )
    argc: argument count,  argv: argument variables,char指针数组。
7.  字符串常量实质是一个指针。
    "xyz" + 1; // 字符串"xyz"的地址加1。
    *"xyz"; // 表达式结果为'x'。
    "xyz"[2]; // 表达式结果为'z'。
 

8.       函数只能返回标量值,不能返回数组。

9.       对函数指针进行操做以前,必须把它初始化为指向某个函数,函数指针的初始化也能够经过赋值操做进行完成;在函数指针的初始化以前,具备函数的原型是很重要的。 

10.     把具体操做和选择操做份开始一个良好的程序设计方案。

11.   只有当确实须要时,才应该使用多层间接访问,否则程序将会变得更庞大,更缓慢而且难以维护。

12.   不一样寻常的代码应该加上相应的注释。

14.调用函数:

int  ans;

ans = f(25);

ans = (*pf)(25);

ans = pf(25);

   第一种调用函数过程:首先函数名f被转换为一个函数指针,该指针指定函数在内存中的位置,而后函数操做符调用该函数,执行开始于这个地址的代码;

  第二种调用函数过程:执行函数操做符以前,将函数指针转换为函数名,其后的过程与前者同样;

  第三种调用函数过程:省略了将函数名转换为函数指针,直接执行开始与这个地址的代码。

 

第十四章 预处理器
1.  预约义符号:
    符号        示例             含义
    __FILE__    "name.c"         进行编译的源文件名
    __LINE__    25               文件当前行的行号
    __DATE__    "Jan 31 1997"    文件被编译的日期
    __TIME__    "18:04:30"       文件被编译的时间
    __STDC__    1                若是编译器遵循ANSI C,值就为1,不然未定义。
    Note:先后各2个下划线。
2.  #define  reg         register
    #define  do_forever  for(;;)
    #define  CASE        break; case
    不要在宏定义的末尾加上分号,这会破坏代码的可阅读性。
3.  宏仅仅是替换。定义宏时要使用括号:替换的数据使用括号,整个宏使用括号。宏适于类型无关的,宏的命名约定:一种方式是都大写,
4.  宏不能够出现递归,即不可自我调用。
5.  "#argument"结构被预处理器翻译为:"argument",即转换为字符串。
    "##"结构被预处理处理为把它两边的符号链接成一个符号。
    #define  paster(n)  printf("token"#n"=%d\n", token##n)
    int  token9 = 10;
    paster(9); // Result show in Screen: "token9=10"
6.  预处理移除指令:#undef  symbol
7.  条件编译:
    #if  constant-expression
        Statements
    #elif  constant-expression
        Statements
    #else
        Statements
    #endif
    是否认义symbol:
    #if  defined(symbol)
    #ifdef  symbol 
    #if  !defined(symbol)
    #ifndef  symbol
    在"#define symbol"中虽然symbol的值是一个空字符而不是1,可是symbol被定义。
8.  #error指令用于生成错误信息。
    #error  text of error message
    #line指令通知预处理器number是下一行输入的行号,若有string,则会把string做为当前的文件名。(修改了__LINE__和__FILE__)
    #line  number "string"
    #progma指令用于支持因编译器而异的特性。
9.    c与处理器要作的事情:删除注释,插入#include包含的内容文件的内容,定义和#define指令定义的符号以及肯定代码的部份内容是否应该根据一些条件编译指令进行编译

10.   #define的基本用法:

 #define   name stuff  

使用#define指令,能够把任何文本替换到程序中;(若是定义中的stuff很是长,能够分红几行,除了最后一行外,每行的末尾都要加一个反斜杠,,而且不要加上;号)。

11.#define定义符号和宏的三个步骤:

(1)在调用宏时,首先对参数进行检查,看看是否包含了任何由#define定义的符号,若是是,它们首先替换;

(2)替换文本随后被插入到程序中原来文本的位置,对于宏,参数名被它们的值所替代;

(3)最后,再次对结果文本进行扫描,看看它是否包含了任何由#define定义的符号,若是是就重复上述处理过程。

   在这个过程当中,应注意如下问题:

(1)宏参数和#define定义的能够包含其余#define定义的符号,可是宏不能够出现递归;

(2)当预处理器搜索#define定义的符号时,字符串常量的内容并不进行检查。

12.宏参数插入到字符常量中的两个技巧:

(1)只有当字符串常量做为宏参数给出时才能使用

#define  PRINT(FORMAT, VALUE)        \  

                    printf("The value is "FORMAT"\n", VALUE)  

 

PRINT("%d", x + 3);  

(2)使用预处理把一个宏参数转化为一个字符串(#argument被预处理器翻译为“argument”)   将输出:The value of x + 3 is 25

 

#define  PRINT(FORMAT, VALUE)        \  

                    printf("The value is "FORMAT"\n", VALUE)  

 

PRINT("%d", x + 3); 

 

13.##结构做用是将位于它两边的符号链接成一个符号(容许宏定义从分离的文本片断建立标识符)

#define ADD_TO_SUM(sum_number, value)        \ 

sum ## sum_number += value

ADD_TO_SUM(5, 25);

产生的结果是将25的加到sum5中(这种链接必须产生一个合法的标识符,不然结果是未定义的)。

14.宏常常应用于执行简单的计算,至于为什么不用函数来完成一些简单的运算,有如下两个缘由:

(1)用于调用和从函数返回的代码有可能比实际执行这个小型计算工做的代码更大,所以使用宏比使用函数在程序的规模和速度方面都更胜一筹;

(2)函数的参数必须声明为一个特定的类型,可是宏是与类型无关的;

可是宏也有其不利之处:使用宏会增长程序的长度,而且使用具备反作用的参数可能在宏的使用过程当中产生不可预料的结果。

15.宏和函数的不一样之处:

       

16.条件编译能够用于调试程序和在编译时选择不一样的代码部分。

17.编译器支持两种不一样类型的文件包含:函数库文件和本地文件:

#include <filename> 
#include "filename"
前者编译器是在编译器定义的“一系列标准位置”查找函数库头文件,后者编译器是源文件所在的当前目录下进行查找。

18.可使用条件编译的方法来解决多重包含的问题

#ifndef   _HEADERNAME_H

#define  _HEADERNAME_H  1

#endif 

用上述的方法就能够很好的消除多重包含的危险。

 
第十五章 输入/输出函数
0.  stdio.h:Refer File
    typedef struct {
        short           level;      /* fill/empty level of buffer */
        unsigned        flags;      /* File status flags */
        char            fd;         /* File descriptor */
        unsigned char   hold;       /* Ungetc char if no buffer */
        short           bsize;      /* Buffer size */
        unsigned char   *buffer;    /* Data transfer buffer */
        unsigned char   *curp;      /* Current active pointer */
        unsigned        istemp;     /* Temporary file indicator */
        short           token;      /* Used for validity checking */
    } FILE; 
    #define feof(f)     ((f)->flags & _F_EOF)
1.  void  perror(char const *message); // 报告错误
2.  void  exit(int status); // 停止执行,status返回给操做系统
3.  Fully Buffered:彻底缓冲,读取和写入的动做在一块被称为缓冲区(buffer)的内存block进行,当buffer 写满的时候才会执行刷新操做(flush)。 这就致使咱们写入buffer的数据,并不会立马写入(显示或存储);但一次性把写满的buffer写入比逐片把程序的输出写入效率要高。同理,buffer为空时经过从设备或文件读取一块较大的输入,从新填充buffer。在使用printf函数进行debug的时候,最好在调用printf以后强制刷新(fflush)缓冲区。
    printf ("something is wrong");
    fflush (stdout);
4.  流分为两种:Text stream 和 Binary stream。
5.  FILE数据结构用于访问流,每一个流都应有相应的FILE与其关联。
    Three stream:standard input(stdin),standard output(stdout),standard error(stderr); 它们都是指向FILE结构实例的指针。
6.  EOF:提示到达文件尾,它的实际值比一个字符要多几位,这是为了不二进制值被错误的解释为EOF(EOF不在0~255以内),使用feof(FILE *stream)宏判断是否到了文件尾。
7.  文件流I/O操做步骤:
    A. 声明一个FILE指针;
    B. 调用fopen函数打开流,返回FILE结构的指针(为了打开一个流,必须指定须要访问的文件或设备,以及访问方式,如读、写、读写…);
    C. 根据须要对流进行操做;
    D. 调用fclose函数关闭流;
8.  标准流的I/O不须要打开或关闭,即所谓的I/O函数,可直接使用。(stdin,stdout)
9.  FILE  *fopen(char const *name,  char const *mode); //失败返回NULL,应该始终检查fopen函数返回的指针是否为NULL。
    FILE  *freopen(char const *name,  char const *mode, FILE *stream); // 函数首先试图关闭这个流,而后用指定的文件和模式从新打开这个流;失败返回NULL,成功返回他的第三个参数值。
10. int fclose(FILE *f); // 对于输入流,fclose函数在文件关闭以前刷新buffer;若是执行成功,fclose返回0,不然返回EOF。
11. 字符I/O:
    int  fgetc(FILE *stream); //不存在字符,返回EOF,是函数。
    int  getc(FILE *stream); //不存在字符,返回EOF,是宏。
    int  getchar(void); // stdin:标准输入流。
    int  fputc(int character,  FILE *stream); //是函数。
    int  putc(int character,  FILE *stream); //是宏。
    int  putchar(int character ); // stdout:标准输出流。
    int  ungetc(int character,  File *stream); // 把一个先前读入的字符返回到流中。
    若是一个流容许退回多个字符,那么这些字符再次被读取的顺序是退回时的反序。
12. 未格式化的行I/O:
    char  *fgets(char *buffer,  int buffer_size,  FILE *stream); //到达文件尾部返回NULL,不然返回他的第一个参数,这个返回值一般用来检查是否到了文件尾。
    char  *gets(char *buffer);
    int  fputs(char const *buffer,  FILE *stream); //写入错误返回EOF,不然返回一个非负值
    int  puts(char const *buffer);
    fgets没法将字符串读入到一个长度小于两个字符的缓冲区,由于其中一个字符须要为NUL字节保留。
    Buffer的长度由常量MAX_LINE_LENGTH决定,也就是读取一行文本的最大长度。
13. 格式化的行I/O:
    int  fscanf( FILE *stream,  char const *format, … ); // scan form file
    int  scanf( char const *format, … );
    int  sscanf( char const *string,  char const *format, … ); // scan from string
    上述格式化输入:返回值为被转换的输入值数目。
    int  fprintf( FILE *stream,  char const *format, … ); 
    int  printf( char const *format, … );
    int  sprintf( char *buffer,  char const *format, … ); 
    上述格式化输出:返回值是实际打印或存储的字符数。
14. 二进制I/O:二进制避免了在数值转换为字符串的过程当中所涉及的开销和精度损失。
    size_t  fread(void *buffer,  size_t size,  size_t count, FILE *stream);
    size_t  fwrite(void *buffer,  size_t size,  size_t count, FILE *stream);
    buffer为缓冲区,被解释为一个数组,size是缓冲区每一个字符的字节数,count指定传输缓冲区中多少值,返回值是实际读取或写入的元素数目。
15. int  fflush(FILE *stream); // 迫使一个输出流缓冲区内的数据进行物理写入,不管它满或没满。
    long  ftell(FILE *stream); // 返回流的当前位置,ftell的值总能够用于fseek
    int  fseek(FILE *stream,  long offset,  int from); // 用于在流中定位,from:SEEK_SET, SEEK_CUR, SEEK_END,若是form是SEEK_SET,offset必须是同一个流中之前调用ftell返回的值。
    void  rewind(FILE *stream); // 将读/写指针设置回指定流的的起始位置,同时清除流的错误提示标志
    int  fgetpos(FILE *stream,  fpos_t *position); // 功能相似ftell
    int  fsetpos(FILE *stream,  fpos_t const *postion); // 功能相似fseek
16. 改变缓冲方式:
    void  setbuf(FILE *stream,  char *buf); // 设置另外一个数组对流进行缓冲,长度必须为BUFSIZ(stdio.h); 若是参数为NULL,将关闭流的全部缓冲方式。
    int  setvbuf(FILE *stream,  char *buf,  int mode,  size_t size); //  mode:指定缓冲的类型,IOFBF指定一个彻底缓冲的流,IONBF指定一个不缓冲的流,IOLBF指定一个行缓冲流(每当有换行符写入到缓冲区,缓冲区进行刷新);size为buffer的长度。
    若是须要一个很大的缓冲区,它的长度应该是BUFSIZ的整数倍。
17. 流错误函数:
    int  feof(FILE *stream); // 处于尾部返回为真,这个状态可经过fseek、rewind、fsetpos清除。
    int  ferror(FILE *stream); // 报告流的错误状态,如出现任何读/写错误返回真
    void  clearerr(FILE *stream); // 清除流的错误标志
18. Temp File:
    FILE  *tmpfile(void); // 文件被关闭或程序终止时临时文件被自动删除;建立的文件以wb+模式打开,可用于二进制和文本数据。
    char  *tmpnam(char *name); // 建立临时文件的名字,调用次数不超过TMP_MAX时,每次都产生一个新的不一样名字。参数为NULL是,返回一个指向静态数组的指针,数组包含了被建立的文件名。
19. 文件操做函数:执行成功返回0,失败返回非零值
    int  remove(char const *filename); // 文件被打开时调用remove,其结果取决于编译器。
    int  rename(char const *oldname,  char const *newname);
20.  流错误函数,临时文件函数tmpfile,文件操纵函数。

21.    良好的编程实践要求任何可能产生错误的操做,都应该在执行以后进行检查,肯定它是否成功。

22.    注意只有当库函数失败时,errno才能被设置,当函数成功运行时,errno的值不会被修改。

23.    exit 函数中的参数和main中的参数状态是一致的,用于提示程序是否正常完成,这个函数没有返回值,当exit结束时,程序已经消失,因此他无返回值而言。

24.    标准i/o函数库还引用了缓存i/o的概念,提升了绝大多数程序的效率。

25.    这个函数库存在两个缺点:1。它在某种特定的类型的机器上实现的,并无对其余不一样特性的机器多做考虑。2.设计这发现上述问题后,试图去修正,可是只要他们这么做了这个函数库就不标准了,程序的可移植性就会下降。

26.    使用标准 输入输出时,这种缓存坑引发混淆,只有当他们与交互设备并没有联系时,才会进行彻底缓存。

27.    事实上,若是程序失败,缓存奴输出可能不被写入,这就可能使得关于程序出现错误的位置不正确,这个的解决方法是在用于调适的printf后面加上fflush, fflush迫使缓存区的内容当即写入,无论他当即已满。

28.    标准错误就是错误信息写入的地方。

29.    打开流和关闭流,对关闭流是否进行检验的标准是:问两个问题,操做成功应该执行什么 ,操做失败应该执行什么;若是答案同样的话,能够不进行检验不然进行检验。

30.    fget fput是真正的函数,可是getc putc getchar putchar 都是定义的宏。

31.    二进制数据避免了在数值转换为字符串过程当中所涉及到的开销和精度损失,可是这些机巧只能将数据被另一个数据顺序读取时才能使用。

32.    fflush迫使一个输出流的缓存区内的数据进行物理写入,无论他是否已满。

33.    随机访问是经过读取和写入先前定位到文件中须要的位置来实现。

 
第十六章 标准库函数
1.  stdlib.h:
    int  abs( int value );
    long  int  labs( long int value );
    div_t  div( int numerator,  int denominator );
    ldiv_t  ldiv( long int numer,  long int denom );
    int  rand( void );
    void  srand( unsigned int seed );
    void  srand( (unsigned int) time(0) ); // 天天的时间作为随机数产生器的种子
    int  atoi( char const *string );
    long  int  atol( char const *string);
    long  int  strtol( char const *string,  char **unused,  int base ); // base为将要用的进制
    unsigned  long  int  strtoul( char const *string,  char **unused,  int base );
2.  math.h:
    double  exp(double x); // e值的x次幂
    double  log(double x); // 返回以e为底x的对数
    double  log10(double x); 
    double  frexp( double value,  int *exponent ); // 计算一个指数(exponent)和小数(fraction)
    double  ldexp( double fraction,  int exponent ); 
    double  modf( double value,  double *ipart ); // 把一个浮点值分红整数和小数两个部分
    double  pow(double x,  double y); // 返回x的y次方
    double  sqrt(double x); // 返回x的平方根
    double  floor(double x); // 返回不大于参数的最大整数
    double  ceil(double x); // 返回不小于参数的最小整数
    double  fabs(double x); // 返回参数的绝对值
    double  fmod(double x,  double y); // 返回x/y所产生的余数,商必须为整数
3.  clock_t  clock(void); // 返回处理器时钟滴答的次数,除以CLOCKS_PER_SEC转换为秒数
    time_t  time(time_t *returned_value); // 参数为NULL时返回当前时间,不为NULL参数存储当前时间
    char  *ctime(time_t const *time_value); // 格式化为字符串
    double  difftime(time_t t1,  time_t t2); // 返回t1-t2的时间差,并转换为秒
    sturct  tm  *gmtime(time_t const *time_value); // convert to Greenwich Mean time
    struct  tm  *localtime(time_t const *time_value); // convert to Local time
    time_t  mktime(struct tm *tm_ptr); // convert to time_t structure
    char  *asctime(sturct tm *tm); // covert to string format
    sturct tm成员:tm_sec,tm_min,tm_hour,tm_mday,tm_mon,tm_year,tm_wday,tm_yday,tm_isdat。
    strftime函数把一个tm结构转换为一个根据某个格式字符串而定的字符串。
4.  setjmp的第一次调用确立一个执行点,若是调用longjmp,程序的执行流会在该地点恢复执行。(不能返回到一个已经再也不处于活动状态的函数)
    jmp_buf  restart;
    value = setjmp(restart);
    longjmp(restart, 1);
5.  信号处理函数:signal handler,用于信号发生时程序调用这个函数进行处理。异步信号的处理函数中调用exit或abort函数是不安全的。
6.  终止执行:stdlib.h
    void  abort( void ); // 不正常的终止一个正在执行的程序
    void  atexit( void (func)( void )); // 把一个函数注册为退出函数;atexit函数中不要再调用exit函数,可能会致使无限循环。
    void  exit( status ); // exit函数被调用时,全部被atexit注册的退出函数将按照注册顺序被反序依次调用。
7.  断言:assert.h
    void  assert( int expression ); //判断表达式的真假。为假:向标准错误打印一条诊断信息并终止程序。
    程序在测试完成以后,能够在编译事经过定义NDEBUG消除全部的断言;使用-DNDEBUG编译器命令行选项,或这在源文件的头文件中assert.h被包含以前增长下面的定义:#define NDEBUG,当NDEBUG被定义以后,预处理器将丢弃全部的断言。
8.  环境:stdlib.h
    char  *getenv( char const *name ); //获取环境变量
9.  执行系统命令:stdlib.h
    void  system( char const *command ); // system可使用NULL参数,用于询问命令处理器是否实际存在。
10. 排序与查找:stdlib.h
    void  qsort(void *base,  size_t n_elements,  size_t el_size,  int (*fcmp)(const void *,  const void * ));
    void  *bsearch(const void *key,  const void *base,  size_t nelem,  size_t width,  int (*fcmp)( const void *,  const void * ));
11. Locale.h:一组特定的参数,每一个国家可能各不相同。
    char  *setlocale( int category,  char const *locale );
 

       整型函数库:算术<stdlib.h>取绝对值,除法运算(对整型的运算包含商和余数,返回一个结构),随机数的<stdlib.h>其中有个小技巧:使用每一天的时间做为随机数产生的种子  ;字符串转换<stdlib.h>将字符串转换为数值。

           浮点型函数库,<math.h>包含了剩余数学函数的声明,这些函数的绝大多数返回值都是double型,注意区别定义域错误和范围错误;包含三角函数,双曲函数,对数和指数函数,浮点形式,幂函数,底数,顶数,绝对值和余数<math.h>转换为double型的字符串转换函数,(书上标记的是在<stdlib.h>中,本人认为是笔误应该在<math.h>中)。

           日期和时间函数:<time.h>处理器时间,当天时间其中有一个difftime函数用来计算两个时间的差值。

 
 
第十七章 经典抽象数据类型
1.  内存存储方案:静态数组,动态分配的数组,动态分配的链式结构。
2.  堆栈:后进先出(LIFO)
    先检测,后压栈;压栈:top标志先加1,后赋值。
    先检测,后出栈;出栈:先清值,top标志后减1。(top标志减1便可,没必要删除元素)
3.  队列:先进先出(FIFO),rear进front出。
    循环数组(circular array):数组的头尾相连,可用来作队列的储存,组成环形队列。
    环形队列为空时:front – rear = 1,有一个元素时,front=rear。
    从新定义队列满:数组中的一个元素始终保留不用,这个元素始终在队列空间未使用部分的rear和front之间;则当(rear+1)%QUEUR_SIZE == front为真时,队列为空;当(rear+2)%QUEUR_SIZE == front为真时,队列为满;
4.  二叉搜索树(binary search tree):每一个节点最多具备两个孩子,节点值大于左孩子,小于右孩子。
    前序遍历(pre-order):节点→左子树→右子树
    中序遍历(in-order):左子树→节点→右子树
    后序遍历(post-order):左子树→右子树→节点
    层次遍历(breadth-first):顶层→第二层(左右)→......→最底层(左右)
5.  数组表示二叉搜索树
    规则:根节点从1开始
    A. 节点的双亲节点是N/2
    B. 节点N的左孩子是2N
    C. 节点N的右孩子是2N+1
    规则:根节点从0开始:
    A. 节点的双亲节点是(N+1)/2 - 1
    B. 节点N的左孩子是2N+1
    C. 节点N的右孩子是2N+2
    链式二叉树比数组更适合作树的存储。
6.  #define能够近似的模拟泛型机制。泛型是OOP处理的比较完美的问题之一。
7.  使用断言检查内存是否分配成功是危险的。
 
 
第十八章 运行时环境
1.  虚拟内存:由操做系统实现,在须要时把程序的活动部分放入内存并把不活动的部分复制到硬盘中,这样就能够容许系统运行大型的程序。
2.  是连接器而不是编译器决定外部标识符的最大长度
3.  不能连接由不一样编译器产生的程序。
4.  优化程序:优化算法比优化代码更有效果。

终于整理完了,又花了一下午时间……

 这里还有一篇 

C语言中的高级声明--《c和指针》摘要

 

参考:

 

yanghaoran321http://my.csdn.net/yanghaoran321

②arkhe:http://www.cnblogs.com/arkhe/articles/2615965.html

zhghosthttp://blog.csdn.net/zhghost/article/details/5287865

相关文章
相关标签/搜索