读书笔记

来源:互联网 发布:php 百度地图找房 编辑:程序博客网 时间:2024/04/30 16:57

C语言的两位发明者Ken ThompsonDennis Ritchie致敬

 

※为什么实参char **argv与形参const char **p实际上不能相容?

char **Const char **所指的类型不一样(前者指向char *,后者指向Const char *),因此它们是不相容的。

 

 

※一些重要的关键字:

Static:在函数内部,表示改变量的值在各个调用期间一直保持延续性;在函数这一级,表示该函数只对本文件可见。

Extern:用于函数定义,表示全局可见(属于冗余的);用于变量,表示它在其它地方定义。

Volatile:一般这个修饰符用来告知编译器,被修饰的变量是个“易变的”变量(volatile的本意是“易变的”),防止编译器进行优化。将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化。(1)在中断服务程序中修改的供其它程序检测的变量需要加volatile;(2)多任务环境下各任务间共享的标志应该加volatile;(3)存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;

Register:当对一个变量频繁被读写时,需要反复访问内存,从而花费大量的存取时间。为此,C语言提供了一种变量,即寄存器变量。这种变量存放在CPU的寄存器中,使用时,不需要访问内存,而直接从寄存器中读写,从而提高效率。寄存器变量的说明符是register。对于循环次数较多的循环控制变量及循环体内反复使用的变量均可定义为寄存器变量,而循环计数是应用寄存器变量的最好候选者。(1) 只有局部自动变量和形参才可以定义为寄存器变量。因为寄存器变量属于动态存储方式,凡需要采用静态存储方式的量都不能定义为寄存器变量,包括:模块间全局变量、模块内全局变量、局部static变量;(2) register是一个"建议"型关键字,意指程序建议该变量放在寄存器中,但最终该变量可能因为条件不满足并未成为寄存器变量,而是被放在了存储器中,但编译器中并不报错(在C++语言中有另一个"建议"型关键字:inline)。

 

typedefdefine的区别

首先,可以用其他类型说明符对宏类型进行扩展,但对typedef所定义的类型名却不能这样做。例如:

#define int_type int

Unsigned int_type i /* ok */

#typedef int_type int

Unsigned int_type i /* error */

其次,在连续几个变量的声明中,用typedef定义的类型能够保证声明中所有的声明中所有的变量均为同一种类型,而用#define定义的类型则无法保证。

#define int_ptr int *

Int_ptr a,b;  /* a是一个指向int的指针,b是一个int */

#typedef int_ptr int *

Int_ptr a,b;  /* ab类型相同,都是一个指向int的指针 */

 

 

C程序的处理过程:预处理-》语法、语义分析-》代码生成-》优化-》汇编-》链接载入;

 

 

※静态链接与动态链接

       如果函数库的一份拷贝是可执行文件的物理组成部分,那么我们称之为静态链接(使用工具ar生成.a文件);如果可执行文件只是包含了文件名,让载入器在运行时能够寻找程序所需要的函数库,那么我们称之为动态链接(使用工具ld生成.so文件)。静态链接只装入所需要的函数,并不是全部装入;动态链接则在实际调用时装入。

 

 

※程序在内存中的布局:|| 堆栈-》空洞《-堆 || BSS | 数据段 || 代码段 || 未映射||

内存高地址――――――――――――――》内存低地址

 

 

※堆区域用于动态分配的存储,堆中所有东西都是匿名的――不能按名字直接访问,只能通过指针间接访问。从堆中获取内存的唯一办法就是通过的用malloccallocrealloc等函数。Callocmalloc类似,但是它在返回指针之前先把分配好的内存的内容清零;realloc函数改变一个指针指向内存块的大小,既可以将其扩大,也可以把它缩小,这在动态增长表的时候很有用。使用free函数来回收对内存。

 

 

※导致段错误的常见编程错误:

1)坏指针值错误:在指针赋值之前就用它来引用内存,或者向库函数传送一个坏指针,或者对指针释放之后再访问它的内容。

2)改写错误:越过数组边界写入数据,在动态分配的内存两端之外写入数据,或改写一些堆管理数据结构。

3)指针释放引起的错误:释放同一内存块两次,或释放一块未曾使用malloc分配的内存,或释放一个无效指针。

 

 

※什么时候数组和指针是相同的?

       规则1、表达式中的数组名(与声明不同)被编译器当作一个指向该数组第一个元素的指针。

       规则2、下标总是与指针的偏移量相同。

       规则3、在函数参数的声明中,数组名被编译器当作指向该数组第一个元素的指针。

规则12合在一起理解就是对数组下标的引用总是可以写成“一个指向数组的起始地址的指针加上偏移量”。

 

 

※数组和指针参数是如何被编译器修改的?

“数组名被改写为一个指针参数”规则并不是递归定义的。数组的数组会被改写为“数组的指针”,而不是“指针的指针”。

                     实参                                          所匹配的形式参数

       数组的数组    char c[8][10]            char (*)[10]              数组指针

       指针数组       char *c[15]               char **c            指针的指针

       数组指针       char (*c)[10]       char(*c)[10]  不改变

    指针的指针  char **c          char **c      不改变

       之所以能在main()函数中看到char**argv这样的参数,是因为argv是个指针数组(即char *argv[])。这个表达式被编译器改写为指向指针数组第一个元素的指针,也就是一个指向指针的指针。如果argv事实上被声明为一个数组的数组(也就是char argv[10][15]),它将被编译器改写为char(*argv)[15](也就是一个字符数组指针),而不是char **argv

 

 

※关于C++

       抽象的好处:隐藏不相关的细节,把注意力集中在本质特征上;向外部世界提供一个“黑盒子”的接口;把一个复杂的系统分解成几个相互独立的组成部分;重用和共享代码。

       访问控制:publicprotectedprivatefriendvirtual。其中friendvirtual后面不跟冒号,每次只能用于一条声明,而publicprotectedprivate可以跟一大串声明。

       继承:从一个类派生另外一个类,使前者所有的特征在后者中自动可用。它可以声明一些类型,这些类型可以共享部分或全部以前所声明的类型。它可以从超过一个的基类类型共享一些特征。

       重载:就是简单地复用一个现存地名字,但使它操作一个不同的类型。它可以是一个函数的名字或者是一个操作符。

       多态:是支持相关的对象具有不同的成员函数(但原型相同),并允许对象与适当的成员函数进行运行时绑定。C++通过覆盖支持这种机制――所有的多态成员函数具有相同的名字,由运行时系统判断哪一个最为合适。这个判断并调用正确的函数的过程被称为“后期绑定”。在成员函数前面加上关键之virtual告诉编译器改成员函数是多态的。

       C++的其它概念:异常、模板、内联函数、newdelete、传引用调用等等。

 

 

C++对C语言的改进和限制

       增加初始化字符数组的检查,例如char b[3] = Bob”这样的表达式在C中合法,在C++中被认为是一个错误。

       类型转换可以写成float(i)或者(float)i

       C++允许一个常量整数来定义数组的大小。

       声明可以穿插于语句之间。而不是在C语言中,一个语句块中所有的声明都必须放在所有语句的前面。

       C++中,用户代码不能调用main()函数,但C语言允许。

       完整的函数原型声明在C++中是必须的,但是C却没有这么严格。

       C++中,由typedef定义的名字不能与已有的结构标签冲突,但C语言中确实允许的,分属不同的名字空间。

       Void*指针付给另一个类型的指针时,C++规定必须强制转换,但C中无必要。

       C++至少增加了十几个关键字。

       C++中内层对象名会隐藏外层空间对象名,C中不会。

       C++中,字符常量的类型是char,但在C语言中,它们的类型是int,也就是说,在C++中,sizeof(‘a‘)的结果是1,而C中则要大一些。

       C++中增加了新的//注释符。

 

 

※怎样才能检测到链表中存在循环?(限制:只读,不能标记,内存有限,不能用外部数组,链表长度任意,循环可能出现在任何位置)

       设置两个指针p1p2p1指向第一个元素,p2指向第三个元素,看看他们是否相等。如果不等,把p1向后移一个元素,p2移两个元素。检测两个指针的值,如果相等,说明链表中存在循环。如果不等,继续按照前述方法进行。如果两个指针均为NULL,则不存在循环。如果存在循环,其中一个指针一定能够追上另一个指针,尽管可能要经过几次遍历才能检测出来。

 

 

※库函数调用和系统调用的区别

       简明的回答是库函数调用是语言或应用程序的一部分,而系统调用是操作系统的一部分。系统调用是在操作系统内核发现一个“trap”或中断后进行的。

       库函数调用:在所有ANSI C编译器版本中,C函数库是相同的;它调用函数库中的一个程序;于用户程序相联系;在用户地址空间执行;运行时间属于用户时间;属于过程调用,开销较小;在libc中大约有300个程序;典型库函数调用:Systemfprintfmalloc

       系统调用:各个操作系统的系统调用是不同的;他调用系统内核的服务,是操作系统的一个进入点,在内核地址空间执行,它的运行时间属于系统时间,需要在切换到内核上下文环境后切换回来,开销较大,在unix中大约有90个调用(MS-DOS中少一些),典型系统调用:chdirforkwritebrk

 

 

※文件描述符与文件指针有何不同?

       文件描述符就是开放文件的每个进程表的一个偏移量。他用于unix系统调用中,用于标示文件。

       FILE指针保存了一个FILE结构的地址。FILE结构用于表示开放的I/O流。它用于ANSI C标准I/O库调用中,用于标识文件。

 

 

原创粉丝点击