C语言程序设计-现代方法(笔记3)
第十三章 字符串
1、字符串字面量(13.1)
字符串字面量:用一对双引号括起来的字符序列。字符串字面量可以像字符常量一样包含转义字序列。
在字符串字面量中小心使用八进制和十六进制的转义序列。
字符串字面量太长而无法放置在单独一行以内,要把第一行用字符\结尾。字符\可以用来把两行或更多行的代码连接成一行。
本质上,C语言把字符串字面量作为字符数组来处理。
2、字符串变量(13.2)
用一个变量来存储最多有80个字符的字符串
#define STR_LEN 80
...
char str[STR_LEN + 1]
声明用于存放字符串的字符数组时,要保证数组的长度比字符串的长度多一个字符。
字符串变量的声明可以省略长度,编译器会自动计算长度。char date4[] = "June 14";
3、使用C语言的字符串库(13.5)
程序需要包含string.h文件,常用有strcpy函数,strlen函数,strcat函数,strcmp函数。
strcpy函数原型:将s2中字符串复制到s1中,包括空字符。
char *strcpy(char *s1, const char *s2);
strlen函数原型:返回字符串s的长度,不包括空字符。
size_t strlen(const char *s);
strcat函数:把字符串s2的内容追加到字符串s1的末尾。
char *strcat(char *s1, const char *s2);
strcmp函数:利用字典顺序进行字符串比较。
int strcmp(const char *s1, const char *s2);
4、字符串惯用法(13.6)
搜索字符串的结尾
//惯用法1
while (*s)
s++;
//惯用法2
while (*s++)
;
复制字符串
//惯用法
while (*p++ = *s2++)
;
5、字符串数组(13.7)
指针数组定义:char *planets[];应用于命令行参数.
int main(int argc, char *arcv[]),argc是命令行参数的数量,argv是命令行参数的指针数组,argv[0]指向程序名,argv[i]指向余下的命令行参数。
第十四章 预处理器
1、预处理器的工作原理
预处理器将输入的C程序进行处理,处理过程包括执行指令和删除指令等,然后预处理器输出另一个C程序,此C程序是原程序编辑后的版本,不再包含指令。
预处理输出的C程序会交给编译器进行检查程序错误。
2、预处理命令(14.2)
预处理命令包括:宏定义(#define),文件包含(#include),条件编译(#if,#ifdef,#endif等)。
预处理命令的规则:指令都已#开始;在指令的符号之间可以插入任意数量的空格或水平制表符;指令均在第一个换行符处结束,除非使用\字符;
指令可以出现在程序中的任何地方;注释可以与指令放在同一行。
3、宏定义(14.3)
宏定义包括:简单的宏和带参数的宏。
#define宏定义优点:程序更易读;程序易于修改;控制条件编译,等。
带参数宏的名字和左括号之间必须没有空格。
带参数宏优点:程序会快些;宏更“通用”。
带参数宏缺点:编译后的代码会变天;宏参数没有类型检查;无法用一个指针来指向一个宏,等。
宏定义包括两个专用运算符:#运算符和##运算符,#运算符可以将一个参数转换为字符串字面量,##运算符可以将两个记号“粘合”在一起,成为一个记号。
宏的通用属性:1.宏的替换列表可以包含对其他宏的调用;2.预处理器只会替换完整的记号,而不会替换记号的片段;3.宏定义的作用范围通常到这个宏的文件末尾; 4.宏不可以被定义两遍;5.宏可以使用#undef指令“取消定义”。
宏加圆括号的规则:1.如果宏的替换列表中有运算符,需要将替换列表放在圆括号中;2.如果宏有参数,每个参数在替换列表中出现时都要放在圆括号中。
创建较长的宏,可以通过加\字符解决。
一些可能用到的预定义宏:__LINE__:被编译的文件中的行号;__DATA__:编译日期(mm dd yyyy);__TIME__:编译时间(hh:mm:ss)。可用于调试,当然还有assert宏。
4、条件编译(14.4)
条件编译时指根据预处理器执行的测试结果来包含或排除程序的片段。
#if指令和#endif指令:#if指令会把没有定义的标识符作为是值为0的宏对待。
defined运算符:应用于标识符时,如果标识符是被定义过的宏则返回1,负责返回0.
#ifdef指令和#ifndef指令:#ifdef指令等价于#if defined;#ifndef指令等价于#if !defined
#elif指令和#else指令:
#if 表达式1
//执行表达式1成立时代码
#elif 表达式2
//执行表达式2成立时代码
#else
//执行其他代码
#endif
条件编译的使用:1.编写在多台机器或多种操作系统之间可移植的程序;2.编写可以用不同的编译器编译的程序;3.为宏提供默认定义;4.临时屏蔽包含注释的代码。
其他指令:#error,#line,#pragma.
第十五章 编写大型程序
1、源文件(15.1)
源文件扩展名为.c。每个源文件包含程序的部分内容,主要是函数和变量的定义。
把程序分成多个源文件优点:1.将相关函数和变量放在同一文件使程序结构更清晰;2.可以分别对每一个源文件进行编译;3.将函数分组放在不同源文件中更利于复用。
2、头文件(15.2)
通过#include指令,可以在任意数量的源文件中共享信息(函数原型、宏定义、类型定义等)。
#include指令:#include <文件名>,用于C语言自身自身库的头文件。#include "文件名",用于其他头文件,包括任何自己编写的文件。
可移植性技巧:在#include指令中不包含路径或驱动器的信息。以下使用相对路径的指令好
#include "utils.h" #include "..\include\utils.h"
将宏定义和类型定义放在头文件中:1.不将定义复制到需要他们的源文件中可以节约时间;2.程序更容易修改;3.无需担心源文件包含相同宏或类型而导致的矛盾。
函数原型共享:在含有函数f定义的源文件中始终包含声明函数f的头文件。
共享外部变量声明:将共享变量的声明放置在头文件中。
保护头文件进行多次包含:使用#ifndef和#endif指令来封闭文件的内容。
#ifndef BOOLEAN_H #define BOOLEAN_H #define TRUE 1 #define FALSE 0 typedef int Bool; #endif
第十六章 结构、联合和枚举
结构是可能具有不同类型的值(成员)的集合。联合和结构很相似,不同之处在于联合的成员共享同一存储空间。枚举是一种整数类型,它的值由程序员来命名。
1、结构变量(16.1)
每个结构都为他的成员设置了独立的名字空间(name space),声明在结构作用域内的名字不会和其他名字冲突。
结构初始化:类似于数组初始化式的原则。对结构操作通过.运算符。
结构可以进行赋值运算:part1 = part2;即使结构中的数组也可以得到复制。
对于结构不能使用运算符==和!=来对结构进行判定。
2、结构类型的命名(16.2)
使用“结构标记”声明;使用typedef来定义类型。
//结构标记方式 struct part { int number; char name[10]; int on_hand; }; struct part part1, part2; //变量声明 // 使用typedef来定义类型名 typedef struct { int number; char name[10]; int on_hand; } Part; Part part1, part2; //变量声明 part2 = part1; //结构赋值
注意:结构用于链表时,强制使用声明结构标记。
数组可以有结构作为元素,结构也可以包含数组和结构作为成员。
初始化结构数组原因是,将其作为程序执行期间不改的信息的数据库。
3、联合(16.4)
联合也是由一个或多个成员构成,成员具有不同类型,区别是编译器只给联合中最大的成员分配足够的内存空间。
联合声明方式类似于结构。
//联合声明,类似于结构声明。 //注意同命名结构类型进行区分: //命名结构类型有结构标记和typedef类型方式。 union { int i; double d; } u = {0};
联合作用:1.节省空间;2.构造混合的数据结构;3.为联合添加“标记字段”。
4、枚举(16.5)
枚举和结构、联合的声明方法很类似。命名也类似。
//枚举声明 enum {CLUBS, DIAMONDS, HEARTS, SPADES} s1, s2; //枚举命名:标记方式 enum suit {CLUBS, DIAMONDS, HEARTS, SPADES}; enum suit s1, s2; //枚举命名:typedef方式 typedef enum {CLUBS, DIAMONDS, HEARTS, SPADES} Suit; Suit s1, s2;
在系统内部,C语言会把枚举变量和常量作为整数来处理。当没有为枚举常量指定值时,它的值比前一个常量的值大1。
第十七章 指针的高级应用
1、动态存储分配(17.1)
动态存储分配:在程序执行期间分配内存单元的能力。主要应用于字符串、数组和结构。可以链接成表、树或其他数据结构。
包含<stdlib.h>头文件,使用malloc函数(最常用,无需对内存块进行清零),calloc函数(分配内存并清零)或realloc函数。
三个内存分配函数返回void *类型的值。是“通用”指针,本质是内存地址。
调用内存分配函数时,会存在找不到满足所需的大小的内存,这是函数返回空指针(NULL)。程序需对空指针进行判定。
2、动态分配字符串和数组+释放存储空间(17.2-17.4)
malloc函数原型:void *malloc(size_t size);使用malloc函数为字符串分配内存空间时,不要忘记包含空字符的空间。
p = malloc(n+1);此为字符串分配内存空间。p = malloc(n * sizeof(int));此为数组分配内存空间。
malloc函数获取的内存成为堆,使用free函数释放不再使用的内存。
free函数原型:void free(void *ptr);free函数的实际参数必须是先前由内存分配函数返回的指针。
p = malloc(...); q = malloc(...); free(p); p = q;
试图访问或修改释放掉的内存块会导致未定义的行为。
3、链表(17.5)
链表:由一连串的结构(结点)组成的,其中每个结点都包含指向链中下一个结点的指针。
链表声明;创建结点;插入结点;搜索链表;删除结点。
4、函数指针(17.7)
第十八章 声明
1、声明的语法(18.1)
声明为编译器提供有关标识符含义的信息。声明形式如下:声明说明符 声明符;
声明说明符:描述生命的变量或函数的性质。声明符:给出了他们的名字,并且可以提供关于其性质的额外信息。
声明说明符分为三类:存储类型(auto、static、extern和register),类型限定符(const和volatile),类型说明符(void,char,short,unsigned).
2、存储类型(18.2)
存储类型:用于变量及较小范围的函数和形式参数的说明。
变量的性质:存储期限,作用域,链接。
static存储类型注意区分块内部和外部的区别。extern存储类型使几个源文件可以共享同一个变量。
4个存储类型中extern和static最重要。auto没有任何效果,现代编译器已经使register变得不如以前重要。
3、类型限定符和声明符(18.3-18.4)
类型限定符:const和volatile。const用来声明一些类似于变量的对象,这些变量是“只读”的。程序能访问const对象的值,但无法改变它的值。
声明符:声明符包含标识符,标识符前面可能有符号*,[]或()组合在一起创建复杂声明符。
声明符包含*、[]和(),例如:*p,a[10],(*pf)(int),*fp(float).
复杂声明符,两个原则:始终从内往外读声明符;在作选择时,始终使[]和()优先于*。 int *(*x[10])(void);
4、初始化式(18.5)
C语言允许在声明变量时为他们制定初始值。(不要把声明中的符号=和赋值运算符相混淆,初始化和赋值不一样)
初始化完全是编译器的行为,赋值则是运行时的行为。最好为静态类型的变量提供初始化式。
第十九章 程序设计
大型程序需要注意:1.编写风格(许多人一起工作);2.正规文档;3.对维护进行规划(程序会多次修改)。
1、模块(19.1)
模块是一组服务的集合,其中一些服务可以被程序的其他部分(客户)使用。每个模块有一个接口来描述所提供的服务。模块的细节都包含在模块的实现中。
在C环境中,这些“服务”就是函数,模块的接口就是头文件。
将程序分割成模块好处:1.抽象;2.可复用性;3.可维护性(最重要)。
好的模块具有:高内聚性(模块中元素应彼此紧密相关)与低耦合性(模块之间应该相互独立)。
模块类型:数据池(相关变量和常量的集合,建议将相关常量放在头文件中);库(相关函数集合);
抽象对象(对隐藏的数据结构进行操作的函数的集合);抽象数据类型(ADT),将具体数据实现方式隐藏起来的数据类型。
第二十章 底层程序设计
1、位运算符(20.1)
可移植性技巧:最好仅对无符号数进行移位运算。
运算符~需要注意:使底层程序有更好的移植性。
位的设置、位的清除和位的测试
//位的设置 i |= 1 << j; //位的清除 i &= ~(1 << j); //位的测试 if (i & 1 << j)
C语言标准库
第二十一章 标准库
第二十二章 输入/输出
第二十三章 库对数值和字符数据的支持