c和指针

C和指针
第一章:
    1,在逻辑上删除代码而不是注释掉用:
        #if 0
            statements
        #endif
    2,#include和#define称为预处理指令,由预处理器解释,预处理器读入源码,根据预处理指令对其修改,然后将修改过的源代码交给编译器。
        列如:#include<stdio.h> ;预处理器用名叫stdio.h的库函数头文件的内容替换#include指令语句,其结果是stdio.h的内容被逐字写到源文件的那个位置。
    3,int func(int ,*)函数原型以一个类型名开头,表示返回值的类型,跟在后面的是函数的名字,后面是参数。
    4,在C语言中,数组参数是以引用形式进行传递的,也就是传址调用,而标量和常量则是按值传递的。在函数中对标量参数的任何修改都会在函数返回时丢失,因此被调用的
        函数无法修改调用函数以传值形式传递给它的参数。当被调用函数修改数组参数时,调用函数传递的数组会被修改。
    5,所有传递给函数的参数都是按传值传递的,但当数组名作为参数时就会产生按引用传递的效果。
    6,gets读取一行,一行的输入由一串字符组成,以换行符结尾。gets丢弃换行符在末尾存储一个NULL(一个NULL字节是字节模式为全0的字节,类似‘\0’,NULL是一个其值为0的指针)
        在c语言中,不存在string类型,但字符串就是以NULL结尾的字符。NULL是其终止符,本身不被看做字符串的一部分。
        字符串常量在源程序中被双引号括起来的一串字符"hello",包含h e l l 0 NULL 在内存中占6个字节的空间。
    7,在scanf中,所有标量都必须加上&,在输入值前的空格、制表符、换行符等都被跳过。
    8,puts函数时gets的输出版本,它把指定的字符串写到标准输出并在末尾添加一个换行符。
    9,int char=getchar() ;whil(char!=EOF&&CH!='\n');为什么ch被声明为整型,而读取的是字符,答案是EOF是一个整型值,它的位数比字符类型要多,声明为整型可防止输入读取的字符意外被
        解释为EOF.但同时意味着接收字符的ch必须足够大,足以容纳EOF.字符只是小整数数而已。putchar与其对应,接收一个整型参数,并在标准输出中打印该字符。
    10,当数组名作为参数传递时,实际传递的是一个指向数组起始位置的指针,即数组在内存中的地址。
    11,strcpy(dest,src):将原有字符覆盖。strncpy(dest,src,nsize):将源中n个字符复制到dest;strcat(dest,src):将src追加到dest末尾。dest必须有足够的空间,函数不对其进行检查。
        strstr(string,char||string):在字符串中搜索字符第一次出现的位置。
    总结:#include预处理指令可以使一个函数库头文件的内容由编译器进行处理,#define指令允许你给字面值常量取个符号名。
第二章
    1,在c的任何一种实现中,存在两种不同的环境,第一种是翻译环境,在这个环境里,源代码被转换为可执行的机器指令。第二种是执行环境,它用于实际代码的执行。    
    编译的步骤:组成程序的每个源文件通过编译过程分别转换为目标代码(object code).然后,各个目标文件由链接器捆绑在一起,形成一个单一而完整的可执行程序。链接器同时也会引入
    标准c函数库中任何可被该程序用到的函数,而且也可以搜索程序员个人的程序库,将其中需要用的函数连接到程序中。
    编译过程本身也有几个阶段组成,首先是预处理器:预处理器在源代码上执行一些文本操作。列如,用实际值代替#define指令定义的符号以及读入由#include指令包含的文件的内容
    然后源代码经过解析,判断语句的意思。第二个阶段是产生绝大多数错误和警告信息的地方。随后产生目标代码。目标代码是机器指令的初步形式,用于程序的语句。如果在编译程序的命令行中加入了
    要求进行优化的选项,优化器会对目标代码进一步处理,使他效率更高。
    2,编译并链接一个完全包含于一个源文件的c程序:cc program.c;这条命令产生一个a.out的可执行程序。中间产生一个program.o的目标文件,但它在链接过程完成后会被删除。
    编译并链接几个c源文件:cc main.c sort.c lookup.c 当编译的源文件超过一个时,目标文件便不会被删除,这就运行你对程序修改后,只对改过的源文件重新编译,如cc main.o lookup.o sort.c
    3, 编译单个c源文件,并产生一个目标文件 cc -c program.c 产生一个目标文件program.o ;编译几个c源文件,并为每个文件产生一个目标文件:cc -c main.c sort.c lookup.c 
    链接几个目标文件:cc main.o sort.o lookup.o ;可以加上-o name 这个选项,它可以使链接器把可执行程序保存在‘name’文件中,而不是a.out;如果在编译时加上-lname标志,链接器会在name的函数库
    中进行查找,这个选项应该出现在命令行的最后。
    4,程序的执行:首先载入到内存中,在宿主环境中(操作系统环境),这个任务由操作系统完成,那些不是存储在堆栈中的尚未初始化的变量在这个时候得到初始值。在独立环境中需手工安排。
    然后程序的执行便开始。在宿主环境中,一个小型的启动程序与程序链接在一起。它负责一系列日常事务如收集命令行参数以便使程序能够访问它们。接着调用main函数。
    现在便开始执行程序的代码。在大多数机器里,程序将使用一个运行时堆栈,用于存储函数的局部变量和返回地址。程序同时也可以使用静态内存,存储于静态内存中的变量在程序的整个执行过程中
    将一直保留它们的值。程序的最后一个阶段就是程序的终止。
    5,c是一种自由形式的语言,不规定一行中,什么地方出现空白,唯一的规则是相邻的标记之间必须出现一致多个空白字符或注释。
    总结:一个c程序的源代码保存在一个或多个源文件中,但一个函数只能从出现在一个源文件中,把相关函数放在同一个文件内是一种好策略。每个源文件都分别编译,产生对应的目标文件
    然后目标文件被链接在一起,形成可执行程序。编译和最终运行程序的机器有可能相同或不同。程序必须载入到内存中才能执行。在宿主环境中,这个任务由操作系统完成。在自由式环境中,程序常常永久
    存储于ROM中,经过初始化的静态bianlzai程序执行前获得他们的值。程序起点是main函数,大多数环境使用堆栈来存储局部变量和其他数据。
第三章
    1,在C语言中仅有4中基本数据类型-整型、浮点型、指针、聚合类型(如数组和结构等)。缺省的char是signed char 或unsigned char取决于编译器,显示声明可能会出现兼容性问题
    2,如果一个值被当做字符使用,那么把这个值表示为字符常量可以使意思更清晰。value=value=‘0’;它用于表示把一个字符转换为二进制值,更重要的是不管采用何种字符集使用字符常量所产生的总是正确的值
        所以它能提高程序的可移植性。
    3,在程序中使用字符串常量会生成一个“指向字符的常量指针”。当一个字符串常量出现于一个表达式中时,表达式使用的值就是这些字符所存储的地址,而不是字符本身。因此可以把字符串常量
    赋值给一个指向字符的指针,后者指向这些字符所存储的地址。但是你不能把字符串常量赋值给一个字符数组,因为字符串常量的直接值是一个指针,而不是这些字符本身。字符串的赋值、复制采用函数
    4,c语言编译器并不检查程序对数组下标的引用是否在数组的合法范围之内。
    5,char *message=“hello”;看上去初始值是赋值给表达式*message;事实上是赋值给message本身。相当于char *message; message=“hello”;因此声明时*尽量靠近message;
    6,typedef 机制允许为各种数据类型定义为新名字。
    7,int const *pci;是一个指向整型常量的指针。可以修改指针的值,但不能修改它所指向的值。int *const cpi;pci为一个指向整型的常量指针。它的值无法修改,但可以修改它所指向的整型的值
    8,名字常量非常有用,因为它们可以给数值起符号名,否则只能写成字面值形式。用名字常量定义数组的长度或限制循环的计数器能够提高程序的可维护性。如果一个值必须修改,只需修改声明就好。
    9,编译器可以确认4种不同类型的作用域:文件作用域(任何在所有代码块之外声明的标识符都具有文件作用域,从声明到所在源文件结尾处都是可以访问的。在文件中定义的函数名也具有文件作用域
        函数名本身不属于任何代码块。在#include指令包含到其他文件中的声明就好像是直接写在那些文件中一样,他们的作用域并不局限于头文件的文件尾)
        、函数作用域、代码块作用域一对花括号之间的所有语句、原型作用域(只适用于函数原型声明的参数名)。
    10,当组成一个程序的各个源文件分别被编译之后,所有的目标文件以及那些从一个或多个函数库中引用的函数链接在一起,形成可执行程序。如果相同的标识符出现在几个不同的源文件中时,
    标识符的链接属性决定如何处理在不同文件中出现的标识符。标识符的作用域与它的链接属性有关,但这两个属性并不相同。链接属性:external(外部)、internal(内部)、none(无)
    none:当做单独个体。internal:在同一个源文件中所有声明都指向同一个实体,不同源文件的属于不同实体。external链接属性的标识符不论声明多少次,位于几个源文件都标识同一实体。
    11,关键字extern和static用于在声明中修改标识符的链接属性。如果正常情况下具有external链接属性,在它前面加上static关键字则可一个变为internal,;
    缺省状态下的代码块外参数声明和函数定义为external;static int c(int b)可以防止被其他源文件引用。static只对缺省链接属性为external的声明才有改变链接属性的效果。
    12,变量的存储类型是值存储变量值的内存类型。变量的存储类型决定变量何时创建、销毁以及它的值保持多久。有三个地方可以用于存储变量:普通内存、运行时堆栈、硬件寄存器、在这三个地方
    存储的变量具有不同的特性。
        1,变量的缺省存储类型取决于它的声明位置。凡是在任何代码块之外声明的变量总是存储在静态内存中,也就是不属于堆栈的内存,称为静态变量。这类变量无法指定其它存储类型。静态变量在程序运行
    之前创建,在程序执行期间始终存在。它始终保持原先的值,除非给它一个不同的值或程序结束。
        2,在代码块内部声明的变量的缺省存储类型是自动的,存储于堆栈中,称为自动变量。有一个变量auto就是用于修饰这种存储类型的。因缺省状态下,是自动创建一般不用。在程序执行到声明自动变量
        的代码块时,自动变量才被创建,当程序执行流离开代码块时,自动销毁。多次执行则多次创建和销毁,内存位置可能不同。
        3,在代码块内部声明的变量如果加上static,则存储类型从自动变为静态。具有静态存储类型的变量在整个程序执行过程中一直存在。注意,修改变量的存储类型并不表示修改变量的作用域,它仍然
        只能在代码块内部按名字访问。函数的形式参数不能为静态,因为实参总是在堆栈中传递给函数用于支持递归。
        4,关键字register可用于自动变量的声明,提示他们应该存储于机器的硬件寄存器而不是内存中。这类变量称为寄存器变量。通常寄存器变量比存储于内存的变量访问效率更高。但编译器一般只选
        前几个存储于寄存器中,其余的按普通自动变量处理。在典型情况下,把频率最高的变量声明为寄存器变量,在某些计算机中,把频繁执行间接访问操作的指针声明为寄存器,以提高效率。
        把函数的形参声明为寄存器变量,编译会在函数的起始位置生成指令,把值从堆栈复制到寄存器中,问题是这种优化措施节省的时间和空间开销抵不上复制这几个值所用的开销。
        寄存器变量的创建和销毁时间和自动变量相同,但需要额外的工作,在使用寄存器变量的函数返回前,寄存器先前存储的值恢复。当函数执行时,它需要把所用的所有寄存器的内容保存到
        堆栈中,函数返回时,这些值再复制回寄存器。由于寄存器的保存和恢复,某个特定的寄存器在不同时刻保存的值不同,所以机器并提供寄存器变量的地址。
    13.自动变量和静态变量的初始化存在一个重要差别:静态变量的初始化,可以把程序需要初始化的值放在变量将会使用的位置,不需要额外的时间和指令,变量将会得到正确的值,如果不显示
    指定初始值,则默认为0;
    14,自动变量的初始化需要更多开销,因为程序链接时还无法判断自动变量的存储位置。事实上,函数的局部变量在函数的每次调用中可能占据不同的位置。因此自动变量没有缺省的初始值
        。在声明变量的同时进行初始化和先声明后赋值只有风格之差,并无效率之别。
    15,当staitc用于函数定义时或代码块外的变量时,用于修改标识符的连接属性从external改为internal,但标识符的存储类型和作用域不受影响。用这种方式声明的函数或变量只能在声明它们的
    源文件中访问。当用于代码块内部的变量声明时,static关键字用于修改变量的存储类型,从自动变量修改为静态变量,但变量的链接属性和作用域不受影响。此声明方式在程序执行前创建,一直存在
    直到执行完毕后销毁。
    16,代码块外的变量和函数声明默认为external链接属性及存储类型为静态,并不存储与堆栈中,因此在程序执行前创建,一直保存其值直到程序结束。加static可改变链接属性为internal。同名的局部变量hui
    隐藏同名的静态变量。
    17,局部变量不具有链接属性,是自动存储类型即存储于堆栈中。寄存器类型的变量初始值是垃圾。对于函数而言,存储类型不是问题,因为代码总是存储于静态内存中。
    局部变量加external全局变量可以在整个代码块内访问,具有external链接属性,存储于静态内存中
    总结:
        具有externa链接属性的实体在其它语言里称为全局(global)实体,所有源文件的所有函数可以访问它。只要变量不是声明在代码块或函数内部,默认链接属性为external,在内部加external可变为
        全局变量。具有external链接属性的实体总是具有静态存储类型。全局变量在程序执行前创建,在执行过程中始终存在。函数的局部变量在函数开始执行时创建,在函数执行完毕后销毁,但用于
        执行函数的机器指令在程序的整个生命周期内一直存在。局部变量由函数内部使用,不被其它函数引用,默认存储类型为自动存储即堆栈。原因:需要时才分配存储,减少内存的需求量;在堆栈
        上分配存储可以有效实现递归。如果变量值多次调用可以修改它的存储类型,把自动变量改为静态存储。
第四章 语句
    1,c不存在专门的赋值语句,加分号就可把表达式转换为语句。表达式语句只有加了赋值操作才会将结果值存储到堆栈中。
    2,c的if、while语句与其它语言的差别在于,c不具备布尔类型,而是用整型来代替即采用关系操作。
    总结:c并不具备任何输入/输出语句;I/O是通过调用库函数实现的。C也不具备任何异常处理语句,也是通过调用库函数来完成的。在每个switch语句都使用degfault语句。
第五章 操作符合表达式
    1,位的操作:value=value|1<<bit_number;把指定的位置设置为1;value=value&~(1<<bit_number);把指定位清0;value&(1<<bit_number);如果指定位已经被设置为1,则表达式结果为非0值;
    2,a=x=y+3;如果x是一个字符型变量,则y+3的值就会被截去一段,以容纳于字符类型的变量中
    3,*操作符是间接访问操作符,它与指针一起使用,用于访问指针所指向的值。
    4,前缀和后缀形式的增值操作符都复制一份变量值的拷贝,用于周围表达式的值正是这份拷贝,前缀在复制前增加值,后缀是在复制后增加值。
    5,c用整数来表示布尔型值
    6,左值就是那些能够出现在赋值符号左边的东西,需要存储到特定位置,因此需要标识特定位置的地址。右值就是那些出现在赋值符号右边的东西。字面值常量都不是左值
第六章
    1,在要求边界对齐的机器上,整型值存储的起始位置只能是某些特定的字节通常是4或2的整数倍。内存中的每个位置由一个独一无二的地址标识,每个位置包含一个值。每个内存地址存储一个值
    但记住地址太笨拙,所以高级语言通过名字而不是地址来访问内存的位置。名字与内存位置之间的关联是由编译器为我们实现的。但硬件仍然通过地址访问内存位置。
    2,不能简单的通过检查一个值的位来判断它的类型,为了判断值的类型必须观察它的使用方式看是整数型算术指令还是浮点型指令。
    3,指针的初始化是用&操作符来完成的,它用于产生操作数的内存地址。
    4,指针变量和其他变量并无区别,如果是静态则初始化为0,如果是自动的则不会初始化,无论哪种情况,声明一个指向整型的指针都不会创建用于存储整型值的内存空间。
    5,指针变量可以作为左值,并不因为它们是指针而是因为是变量。对指针变量进行间接访问表示我们应该访问指针所指向的位置,间接访问指定一个特定的内存位置。
    6,*&a=25:首先&操作符产生变量a的地址,它是一个指针常量(注意使用这个指针常量并不需要知道它的实际值)。接着,*操作符访问其操作数所表示的地址。在这个表达式中,操作数是a的地址,所以
    值25就存储于a中。
    7,int a=12;int *b=&a;c=&b;c的类型是一个指针,b是一个指向整型的指针,c是一个指针的指针。
    int a=12;int *b=&a;int **c=&b;*操作符具有从右向左的结合性,因此*(*c):从里向外,*c访问c所指向的位置,指向变量b;第二个间接操作符访问这个位置指向的地址即变量a;
    8,char *cp=a;*cp+1;*操作符优先级高于+,所以执行间接访问操作,得到他的值即a;然后取得值的一份拷贝并+1;得到字符b;
    9.指针加法运算的结果是个右值,它位置并未清晰定义,如果没有间接访问操作,则表达式cp+1不是一个合法左值,
    然而间接访问跟随指针访问一个特定位置,*(cp+1)就可以作为左值使用,cp指向的位置就后移一位。
    10,++cp;表达式的结果是增值后的指针的一份拷贝,因为前缀++先增加它的操作数的值再返回这个结果。这个拷贝存储位置并未清晰定义,因此不是一个合法的左值。
    cp++后缀同样增加cp的值,先返回cp值的一份拷贝,再增加cp的值。因此++cp和cp++都是非法的。如果增加间接访问操作符*++cp,这样间接访问操作符作用于增值后的指针拷贝上,所以它的右值是
    后面那个内存地址的值,而左值就是那个位置本身。
    //给定一个指向以NULL结尾的指针列表的指针,在列表中的字符串中查找一个特定的字符
    11,int find_char(char **strings,char value)
        {
            char *string;//当前正在查找的字符串
            //遍历列表中的每个字符串
            while((string=*strings++)!=NULL){
                //观察字符串中的每个字符
                while(*string!='\0'){
                    if(*string++==value)
                        return true;
                }
            }
            return false;
        }
        //这个函数将破坏这些指针,只适用于这组字符串使用一次的情况
    12,int find_char(char **strings,char value)
        {
            //char *string;//当前正在查找的字符串
            //遍历列表中的每个字符串
            while(*strings!=NULL){
                //观察字符串中的每个字符
                while(**strings!='\0'){//**strings:第一个间接访问操作访问指针数组中的当前指针,第二个间接访问操作随该指针访问字符串中的当前字符。
                    if(*(*strings)++==value)//*(*strings)++:括号是需要的,第一个间接访问操作访问列表中的当前指针,增值操作把该指针所指向的那个位置的值+1,
                        但第二个间接访问操作作用于原先那个值的拷贝上,
                        return true;
                }
                strings++//遍历数组的下一行
            }
            return false;
        }
    13,字符指针+1,运算结果产生的指针指向内存中的下一个字符。当一个指针和一个整数量执行算术运算时,整数在执行加法运算前始终会根据合适的大小进行调整。
        float *p+3;这个三将根据float类型的大小进行调整(相乘),实际加到指针上的整型值为12;结果是增加了三个float的大小,而不是三个字节。
    14,数组中的元素存储于连续内存位置中,后面元素的地址大于前面元素的地址。因此指针+1指向数组中下一个元素。&vlause[max_num]:表示数组最后一个元素后面那个内存位置的地址。
    15,两个指针指向同一个数组的元素时才允许相减,得到的结果是两个指针在内存中的距离(数组元素长度为单位而不是字节);p1指向a[i],p2指向a[j];p2-p1=j-i;
        总结:指针指向的是内存位置即地址,必须用间接访问来获得所指向位置存储的值。声明一个指针变量不会自动分配内存,因此在间接访问前必须初始化或指向现有内存或分配动态内存。
第七章
    1,int *find_int(int key,int arr[],int len); //最后的分号区分了函数原型和函数定义的起始部分。原型告诉编译器函数的参数数量和参数类型及返回值类型。
    2,在头文件中包含函数原型,原型具有文件作用域,作用于整个源文件,函数原型只写一次,避免出现不匹配现象;
    3,int *func(void),void表示没有任何参数;
    4,c的函数所有参数都是传值调用,对数组是传址调用。
第八章 数组
    1,数组是具有确定数量的常量值,而指针只是一个标量。
    2,只有在两种场合下数组名不用指针常量来表示,当数组名作为sizeof操作符或单目操作符&操作数时。
    3,&a[0]是一个指向数组第一个元素的指针,即数组名本身的值,等价于char *c=a;
    4,下标引用:*(b+3):指向b指针后移3个的位置。
    5,a[subscript]等价于*(a+subscript);
    6,当根据某个固定数目的增量在一个数组中移动时,使用指针变量将比使用下标产生效率更高的代码,声明为寄存器变量的指针通常比静态内存和堆栈中的指针效率更高。
        那些必须在运行时求值的表达式如&a[size]\a+size;的常量表达式往往代价更高。
    7,指针和数组并不是相等的:声明一个数组时,编译器根据声明所指定的数量为数组保留内存空间,在创建数组名时,它的值是一个常量,指向这段内存空间的起始位置。声明一个指针变量时,
    编译器只为指针本身保留内存空间,并不为任何整型值分配内存空间。指针变量并未被初始化为指向任何现有的内存空间。
    8,当一个数组名作为参数传递给一个函数时,是一份该指针的拷贝,函数如果执行了下标引用,实际是间接访问。
    9,形参被声明Wie一个指向const字符的指针的好处:文档习惯,编译器可以捕捉到任何试图修改数据的意外错误,这类声明允许向函数传递const参数。
    10,函数的参数和局部变量被声明为register变量,产生的代码比静态内存中的变量和堆栈中的变量所产生的代码执行速度更快。
    11,函数原型中一维数组形参无需写明它的元素数目,因为函数并不为数组参数分配内存空间。形参只是一个指针,它指向的是已经在其它地方分配好的内存空间。
    12,数组的初始化类似于标量变量的初始化方式,取决于他们的存储类型,存储于静态内存的数组只初始化一次,即在程序开始执行之前。执行时,不需要把他们放在合适的位置,
    由链接器将它们连接在一起。自动变量位于运行时堆栈中,执行流每次进入它们所在的代码块时,变量每次所处的内存位置并不相同,在程序开始前,编译器没有办法对这些位置进行初始化。
    13,快速初始化字符数组的方法:char message[]="hello"
    14,多维数组:int[3][10]:可以看做一个一维数组,包含三个元素,只是每个元素恰好包含10个整型元素的数组。数组名的值是一个指向第一个元素的指针,所以a是一个包含10个整型元素的数组的指针。
    15,*(a+1)+5:是二维数组先从*(a+1)第一行移到第二行,*(a+1)+5是定位到第二行第五位置,*(*(a+1)+5):是间接访问该位置的值。
    16,指向数组的指针:int a[10],*vp=a;合法;
        int matrx[3][10],*mp=matrix;非法。正确的创建了matrix数组,并把mp声明为一个指向整型的指针,但mp的初始化不正确,因为matrix并不是一个指向整型的指针,而是一个指向数组的指针。
        int (*p)[10]:指向数组的指针。*p是一个指针,所以指向某种类型的数组。其初始化为:int (*p)[10]=matrix;下标不能省略,指向matrxi的第一行。
    17,函数参数的多维数组名的传递方式和一维数组名相同,实际传递的是指向数组第一个元素的指针,区别在于多维数组的每个元素本身是另外一个数组,编译器需要知道它的维数。
    18,在多维数组中,只有第1维才能根据初始化列表缺省的提供,剩余几个维必须显示的写出。
    19,指针数组:int *api[10],下标引用优先级高于间接访问;因此api是某种类型的数组,在取得一个数组元素后进行间接访问。
    20,sizeof(a):计算结果是整个数组所占用的字节数,而sizeof(a[0])是每个元素占用的字节数,二者相除得到数组元素的个数。    
    21,在访问多维数组的元素时,误用逗号分隔下标;在一个指向未指定长度的数组的指针上执行运算。
第九章
    1,c没有显示的字符串数据类型,字符串以字符常量的形式出现或存储于字符数组中,字符串常量适用于那些程序不会对它们进行修改的字符串;所有其他字符串都必须存储于字符数组或动态内存中。
    2,字符串以null结尾,但字符串的长度并不包含null字节。
    3,*strcpy(char *dst,char const *src):把参数src字符串复制到dst参数。如果参数src和dst在内存中出现重叠,其结果是未定义。由于dst参数将进行修改,所以它必须是字符数组或者是一个指向
    动态内存分配的数组指针,不能使用字符串常量。
    4,程序员必须保证目标字符数组的空间足以容纳需要复制的字符串,如果字符串比数组长,多余的字符仍被复制,它将覆盖原先存储于数组后面的内存空间值。strcpy无法解决这个问题。
    5,strcat与strcpy类似,必须保证目标字符数组剩余的空间足以保存整个源字符串。
    6,strcat与strcpy返回一个指向目标字符数组的指针。
    7,strnlen,strncpy,strncat:将src字符串中的n个字符复制到dst,它的结果不会以null字节结尾。
    8,strchr(char const *str,int ch);返回字符第一次出现的位置的指针;strrchr(char const *str,int ch),返回字符最后一次出现位置的指针;注意第二个参数是一个整数。
    9,char *strpbrk(char const *char,char const *group):函数返回一个str中第一个匹配group中任何一个字符的字符位置。
    10,char *str(char const *s1,char const *s2):查找整个s2第一次出现的起始位置返回一个指向该位置的指针。
    11,*memcpy(void *dst,void const *src,size_t length);*memmove(void *dst,void const *src,size_t length);*memcmp(void *dst,void const *src,size_t length);
    *memchr(void *dst,int ch,size_t length);*memset(void *dst,int ch,size_t length);任何类型的指针都可以转换为void*;length为src数组中总的字节数
        *memcpy(void *dst,void const *src,size_t length);如果src和dst以任何形式出现重叠,则结果未定义;
        *memmove(void *dst,void const *src,size_t length);源和目标可以重叠;
    总结:strcpy:把一个字符串复制到一个较短的数组中导致溢出;strcat函数把一个字符串添加到一个数组中,导致数组溢出;    
第十章
    1,和数组名不同,当一个结构变量在表达式中使用时,它并不被替换成一个指针。
    2,typedeg struct {int a;char b;float c;}SIMPLE;SIMPLE是一个类型名而不是结构标签;
    3,void func(struct COMPLEX *cp);(*cp).f访问结构的成员f;对指针执行间接访问将访问的结构,然后点操作符访问一个成员;等价于->f;
    4,结构体的自引用;struct SELF_REF1{int a;struct SELF_REF1 *b;int c; };
    在结构体中,b是一个指针,而不是一个结构,编译器在结构的长度确定之前就知道指针的长度,这种类型的自引用是合法的。b事实上所指向的是同一种类型的不同结构。更加高级的数据结构
    如链表和树都用这种技巧实现,每个结构指向链表的下一个元素或树的下一个分支;
    5,struct {int a;struct SELF_REF1 *b;int c; }SELF_REF1;truct SELF_REF1 *b未定义,类型名直到声明的末尾才定义,所以在结构声明的内部尚未定义。
    6,不完整的声明:一个结构体包含了另一个结构体的一个或多个成员,和自引用结构一样,至少有一个结构必须在另一个结构内部以指针的形式存在。
    如:struct B; struct A{ struct B *partner}; struct B{struct A *partner;};在A的成员列表中需要标签B的不完整声明。一旦A被声明之后,B的成员列表也可以被声明;
    7,间接访问操作随箭头访问结构,结果是整个结构。
    8,结构的存储分配:struct ALIGN{char a;int b;char c;};//系统禁止编译器在一个结构的起始位置跳过几个字节来满足边界对齐要求,因此所有结构的起始存储位置必须是结构中边界要求最严格的
    数据类型所要求的位置。因此,成员a必须存储于一个能够被4整除的地址。
    9,位段的声明和结构类似,但它的成员是一个或多个位的字段,这些不同长度的字段实际上存储于一个或多个整型变量中,因此必须为int,signed int 或unsigned int 类型,其次成员名的后面
    是一个冒号和一个整数,这个整数指定该位段所占用的位的数目;任何可以用位段实现的任务都可以使用移位和屏蔽来实现。位段是不可移植的。
    10,联合union的所有成员引用的是内存中相同的位置。在一个成员不同的联合里,分配给联合的内存数量取决于它的最长成员的长度。为了避免内存浪费,在联合中存储指向不同成员的指针而不是
    直接存储成员本身。所有指针的长度都是相同的,解决了内存浪费的问题。当它决定需要使用哪个成员时,就分配正确的数量的内存来存储它。
    11,联合变量可以被初始化,但初始值必须是第一个成员的类型。
第十一章
    1,数组的元素存储于内存中连续的位置上,当一个数组被声明时,它所需的内存在编译时就被分配,可以用动态内存分配在运行时为它分配内存。
    2,malloc从内存池中提取一块合适的内存,并返回一个指向这块内存的指针。这块内存此时并没有以任何方式进行初始化。参数为字节数目。它分配的是一块连续的内存。
    3,对于要求边界对齐的机器,malloc所返回的内存起始位置将始终能够满足对边界对齐要求最严格的类型的要求。
    4,calloc与malloc的主要区别是在返回前把它初始化为0.calloc:参数包含所需元素的数量和每个元素的字节数。
    5,reallloc用于修改一个原先已经分配的内存块的大小。如果原先内存块无法改变大小,则另外分配一块,将原先的复制到新的内存块上。
    6,动态内存分配最常见的错误就是忘记检查所请求的内存释放成功分配;另一个是操作内存时超出了分配内存的边界。
    7,free释放一块内存的一部分是不允许的,动态分配的内存必须整块一起释放。但realloc可以缩小一块动态分配的内存,有效释放尾部的部分内存。
    8,分配内存后不释放导致内存泄漏。
    总结:1,不检查从malloc函数返回的指针是否为null,访问动态分配的内存之外的区域,向free函数传递一个并非由malloc函数返回的指针。在动态内存被释放后再访问它。
    动态内存分配有助于消除程序内部存在的限制。使用sizeof计算数据类型的长度,提高程序的可移植性。
第十二章:
    1,可以使用结构和指针创建强大的数据结构。
    2,root是一个指向node的指针,所以参数的类型应该是Node**,即指向node指针的指针
第十三章
    1,变量名在函数的作用域内部是未知的,函数拥有的只是一个指向需要修改的内存位置的指针,所以对该指针进行间接访问操作以访问需要修改的变量。
    2,int *f();函数调用操作符()优先级高于间接访问操作符,因此,f是一个函数,它的返回值类型是一个指向整型的指针。
    3,int (*f) ():第一对括号只起到聚组的作用,迫使间接访问在函数调用前进行,使f成为一个函数指针,它所指向的函数返回一个整型值,第二个是函数调用操作符
    4,int * (*f) ():所指向的函数返回一个整型指针。
    5,int  *f[]:下标的优先级更高,所以f是一个数组,它的元素类型是指向整型的指针。
    6,int  (*f[]) ();*f[]是一个元素为某种类型的指针的数组,表达式的末尾()是函数调用 操作符,所以f肯定是一个数组,数组元素类型是一个函数指针。
    7,函数指针和其它指针一样,对函数指针执行间接访问前必须把它初始化为指向某个函数。
    8,函数指针的初始化:int f(int);int (*pf)(int)=&f;函数指针指向f,表达式中&是可选的,因为函数名使用时总是由编译器把它转换为函数指针,&操作符只是显示说明了编译器将隐士执行的任务。
    9,main(argc,**argv):argv数组中第一个参数为程序的名称、
    10,“xyz”+1;因字符串常量实际上是一个指针时,这个表达式就是计算“指针值+1”即结果是个指向y的指针。
    11,*“xyz”:是对一个指针的间接访问操作。
第十四章
    1,c预处理器在源代码编译前对其进行一些文本性质的操作。它的主要任务是删除注释,插入被#include指令包含的文件内容,定义和替换由#define指令定义的符号以及确定代码的部分内容是否应该
    根据一些条件编译指令进行编译。
    2,#define MAX(a,b) ((a)>(b)?(a):(b)):采用此种方法的好处是:使用宏比使用函数在程序的规模和速度方面好,更重要的是,函数的参数类型必须声明为一种特定的类型,所以只能在类型合适的表达式
    上使用。而宏基本数据类型及其他任何可以用>操作符的比较值大小的类型,即宏与类型无关。坏处是每次使用宏定义代码的拷贝都插入到程序中大幅度增加程序的长度。宏定义结尾无分号;
    3,#undef name;移除一个宏定义;
    总结:不要在宏定义末尾加上分号,使其成为一条完整的语句;在宏定义中使用参数,但忘记了在他们周围加上括号;忘了了在整个宏定义的两边加上括号。
第十五章
    1,绝大多数流是完全缓冲的,意味着读取和写入实际上是从一块被称为缓冲区的内存区域来回复制数据。用于输出流的缓冲区只有当它写满时才会别刷新(flush,物理写入)到设备或文件中。
    2,流分为文本流和二进制流。文本流对于不同系统一个不同就是文本行的最大长度。以及文本行的结束方式。ms-dos中以一个回车和一个换行符结尾;linux只使用换行符结尾;
    3,二进制流中的字节完全根据程序编写它们的形式写入到文件或设备中,而且完全根据它们从文件或设备读取的程序读入到程序中,它们并未做任何改变。
    
  

  • 6
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值