[C和指针]笔记——01章~18章
第一章 快速上手
1. 从逻辑上删除一段C代码,最好的办法是使用#if指令
#if 0
Statement
#endif
2. 其他语言中,无返回值的函数称为过程(procedure)
3. 数组做参数的时候是以引用(reference)的方式传递的,即地址传递。在函数中对形式参数的任何修改都不会影响实参,同时形式参数返回时丢失。
4. 字符串是已一串NUL字节结尾的字符。NULL作为字符串终止符,它本身并不被看作字符的一部分。
NUL表示字符串结尾,NULL表示空指针。
第二章 基本概念
1. 三字母词:几个字符组合起来表示另外一个字符
??( [ ??< { ??= #
??) ] ??> } ??/ \
??! | ??' ^ ??- ~
print("You sure??!"); -→ You sure|
转义字符:'\'(反斜杠)加上一个或者多个字符组成。
第三章 数据
1. 变量三属性:作用域(Scope),链接属性(linkage),存储类型(Storage Class)
2. 整形数字之后添加字符L或者l,表示整数为long type;添加字符U或u,表示为unsigned type
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变量只能被用于使用变量的地方。
12. Internal链接属性的标识符在同一源文件内的所有声明中都指向同一实体。
External链接属性的标识符不论声明多少次,位于几个源文件都表示同一实体。
具有external链接属性的标识符,前面加上static关键字可以是它的链接属性变为internal,static只对default属性为external的声明有效果。
13. Static:当用于函数定义时,或用于代码块之外的变量声明时,链接属性从external变为internal,标识符的存储类型和scope不受影响,只能在源文件中访问;当用于代码块内部的变量声明时,static关键字用于修改变量的存储类型,变更为静态变量,变量的链接属性和作用域不受影响。
Type 声明位置 Stack Scope 如果声明为static
全局 所有代码块之外 否 声明处到文件尾 不允许从其他源文件访问
局部 代码块起始处 是 整个代码块 不在stack中,value在程序运行中一直保持
形参 函数头部 是 整个函数 不允许
第四章 语句
1. C没有专门的赋值语句,而是采用表达式语句代替。
2. C没有bool类型,而是用整数类型代替,零为假,非零为真。
3. 跳出多层loop的方法:
A. 使用goto语句。
B. 设置status flag,在每个循环中都去判断status flag。
C. 把所有loop放在一个单独的函数中,使用return语句跳出loop。
第五章 操作符和表达式
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()的操作数为数组名时,返回的是该数组的长度。
判断表达式的长度并不会对表达式求值。
4. 短路求值(short-circuited evaluation):"&&","||","?:",","
(L→R)左操作数的结果决定是否对表达式进一步求值
5. 利用'.'和'->'访问结构体的区别:
s为struct变量时候,使用s.a访问s中的成员a;
s为指向struct变量的指针时,使用s->a访问成员a。
6. 零为假,任何非零值都为真。(C中没有bool类型)
如用一个变量来表示bool value,那么应该始终把它的value设置为0或1。
7. 左值:表示一个存储位置,可以出现在赋值符的左边或右边;
右值:表示一个value,只能出现在赋值符的右边。
8. 算术转换(arithmetic conversion):操作数类型的转换。(类型的提升)
常用方法是在执行运算之前把其中一个(或多个)操作数类型转换为左值的类型。
9. 复杂表达式的求值顺序3要素:操作符的优先级,操作符的结合性,操作符是否控制执行顺序(短路求值)。
优先级只对相邻操作符的执行顺序有效。
非法表达式的求值顺序(规范没有定义)由编译器决定。
第六章 指针
1. 边界对齐:Boundary alignment(内存对齐)
2. 不能简单的通过检查一个值的位来判断它的类型。
数据的意义不在于它的类型,而在于它被使用的方式。
3. 指针的初始化用&操作符来完成,它用于产生操作数的内存地址。
4. 声明的指针必须初始化才可以使用,不然不能确定指针指向的地方。
5. 安全策略:让函数返回独立的值。首先是函数返回的status value, 用于判断操作是否成功;其次是形参pointer,用于在操作成功时返回结果。
6. 指针变量可以做左值,是因为它们是变量(存储地址)。
7. 指针的强制类型转换:
* 100 = 120; // 非法语句,因为间接访问表达式(*)只能作用于指针类型表达式。
* (int *) 100 = 120; // 合法语句,把100从"整型"转换为"指向整型的指针"。(这仅仅是个例子)
8. 指针运算:
指针 +/- 整数:适用于指向数组中某个元素的指针,且要保证不越界。
指针 – 指针:适用于当两个指针都指向同一数组中的元素时。
关系运算:<, <=, >, >=,适用于指向同一数组中的元素的两个指针,表示的意义为哪个指针指向的数组元素更靠前或靠后。
指针运算只有作用于数组中,其结果才是可以预测的。
第七章 函数
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…..)
第八章 数组
1. 不可以使用"="把一个数组的所有元素复制给另一个数组。
2. int array[10];
int *ap = array + 2 ;
C的下标引用和间接表达式是一样的。
ap[0],这个表达式是完全合法的,这种情况下对等的表达式为*(ap+(0))。
2[array],这个表达式也是合法的,转换为间接表达:*(2+(array))。
3. 下标不会比指针更有效率,但指针有时会比下标更有效率。(效率:指针≥下标,如对数组进行循环赋值时。)
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[];
第九章 字符串、字符和字节
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存储位置发生重叠时,可以继续使用。
第十章 结构和联合
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的一个重要的作用是多选一。比如人的性别是两种不同的属性结构,在定义一个人的结构时就可以使用联合来选择男女这两种属性结构
第十一章 动态内存分配
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返回的指针。
4. a > b ? 1 : ( a < b ? -1 : 0); // 三种结果都可以兼顾到。
5. 动态内存分配允许程序为一个长度在运行时才知道的数组分配内存空间。
第十二章 使用结构和指针
1. 链表的存储空间是动态分配的。
2. 单链表是一种使用指针来存储值的数据结构。
3. 双向链表:一个指针指向前一个链表节点,另一个指针指向后一个链表节点。
4. 语句的提炼用来简化程序,消除冗余的语句。
第十三章 高级指针话题
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. 转移表:实质上是一个函数指针数组,通过确定数组中的元素来选择调用相应的函数。
6. 命令行参数:int main( int argc, char *argv[] )
argc: argument count, argv: argument variables,char指针数组。
7. 字符串常量实质是一个指针。
"xyz" + 1; // 字符串"xyz"的地址加1。
*"xyz"; // 表达式结果为'x'。
"xyz"[2]; // 表达式结果为'z'。
第十四章 预处理器
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指令用于支持因编译器而异的特性。
第十五章 输入/输出函数
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);
第十六章 标准库函数
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 );
第十七章 经典抽象数据类型
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. 优化程序:优化算法比优化代码更有效果。