当函数是递归函数时,每次调用它都会产生其自动变量的新副本.静态变量就不会发生这种情况,相反,所有的递归函数都共享一个静态变量.当函数是递归函数时,每次调用它都会产生其自动变量的新副本.静态变量就不会发生这种情况,相反,所有的递归函数都共享一个静态变量.
第11章指针
1.const 是一个C语言的关键字,它限定一个变量不允许被改变。例如:用一个const变量來初始化数组,ANSI C的编译器会报告一个错误。
const int n = 5;
int a[n];
分析:这个问题讨论的是“常量“与“只读变量“的区别。常量肯定是只读的,例如5,“abc“等,肯定是只读的,因为程序中根本没有地方存放它的值,当然也就不能够去修改它。而“只读变量“则是在内存中开辟一个地方來存放它的值,只不过这个值由编译器限定不允许被修改。C语言关键字const就是用来限定一个变量不允许被改变的修饰符(Qualifier)。上述代码中变量n被修饰为只读变量,而不是常量。而ANSI C规定数组定义时维数必须是“常量“,”只读变量“也是不可以的。(常量不等于不可变的变量,但在标准C++中,这样定义的是一个常量,这种写法是对的)。在ANSI C云烟中用enum类型和#define宏來定义常量。
2.指针总是和地址一样么?
通常是,但不总是。在一些计算机上,指针可能是“偏移量“而不完全是地址。
3.如果指针可以只想程序中的数据,那么是指针指向程序代码是否可能?
可能。例如函数指针。
4.是否存在显示指针的值的的方法?
调用printf函数,在格式串中采用转换%p。
第12章指针和数组
1.C语言支持3种(而且只有3种)格式的指针的算术运算(或者地址算术运算):
注:只有p只想数组元素时,指针p上的算术运算才会获得有意义的结果。此外,只有在两个指针指向同一个数组时,指针相减才有意义。对于并非指向数组的指针,指针的运算是“未定义的“。这并不意味着不能这样做,知识意味着不能保证会发生什么。
2.可以用关系运算符(<、<=、>、>=)和判等运算符(==和!=)进行指针比较。只有在两个指针指向同一数组时,用关系运算符进行的指针比较才有意义。
3.在标准C中,即使元素a[N]不存在(数组a的下标从0到N-1),但是对它使用取地址运算符是合法的。因为循环不会尝试检查a[N]的值,所以上述方式下用a[N]是非常安全的。
4.用多维数组作为指针。
int a[10], b[10][10];
注:
使用a作为指针指向元素a[0],但b是指向b[0]而不是b[0[0],C语言认为b不是二维数组而是作为一维数组,且这个一维数组的每个元素又是一维数组。在类型项中,a可以用作是int *型的指针,而b用作指针时则是具有int **型的指针。
5.i[a]与a[i]是一样的。对于编译器而言i[a]等同于*(i+a),也就是*(a+i)(像普通加法一样,指针加法也是可以交换的)。而*(a+i)也就是a[i]。
6.说明了如何使用指针处理二维数组的行中的元素。用相似的方法处理列中的元素是否可行?
是可行的,但是不像行处理那样容易。因为数组是按行存储的,而不是按列。下面循环清楚的表明数组a中列i的元素:
int a[NUM_ROWS][NUM_COLS], i, (*p)[NUM_COLS];
for (p = a; p <= &a[NUM_ROW - 1]; p++)
(*P)[i] = 0;
已经声明了p是指向长度为NUM_COLS的数组的指针,且此数组的元素为整型的。在表达式(*p)pNUM_COLS]中要求*p周围有圆括号。
如果没有圆括号,那么编译器将会把p作为指针的数组而不是指向数组的指针來处理了。第13章字符串
1.根据C语言的标准,当两条或更多挑字符串字面量相连时(仅用空白字符分割),编译器必须把它们合并成单独一条字符串。例:
printf("Put a disk in drive A, then"
"press any key to continue\n");
3.C语言允许对指针添加下标,因此可以给字符串字面量添加下标:
char ch;
ch = "abc"[1];
ch的新值将是字母b。
4.允许改变字符串字面量中的字符,但不推荐这么做:
char *p = "abc";
*p = 'b'; /*string literal is now "bbc" */
注:对于一些编译器而言,改变字符串字面量可能会导致程序运行异常。
puts(str); puts函数只有一个参数,此参数就是需要显示的字符串,参数中没有格式串。在写完字符串后,puts函数总会添加一个额外的换行符,因此显示会移至下一输出的开始。
6.scanf函数和gets函数读字符串
scanf("%s", str); 在scanf函数调用中,不需要在str前添加运算符&。因为str是数组名,编译器会自动把它当作指针來处理。调用时,scanf函数会跳过空白字符,然后读入字符,并且把读入的字符存储到str中,直到遇到空白字符为止。scanf函数始终会在字符串末尾存储一个空字符。用scanf函数读入字符串永远不会包含空白字符。赢此,scanf函数通常不会读入一整行输入。
gets(str); gets函数同scanf函数类似,把读入的字符放到数组中,然后存储一个空字符。然而,在其他方面gets函数有些不同于scanf函数:
gets函数和puts函数通常比scanf函数和printf函数运行更快。
7.在strcpy(str2, str1)的调用中,strcpy无法检查str1指向的字符串的大小是否真的合适str2指向的数组。如果str1指向更长的字符串,那么结果就无法预测了。(由于strcpy函数会一直复制到str1的第一个空字符为止,所以它会越过str2指向的数组的边界继续复制。无论原来存放在数组后面内存中的是什么,都将被覆盖)。strcat有同样的问题。
8.在strcmp中字符是按ASCII的字符顺序:
第14章预处理器
1.预处理指令:大许哦书预处理指令属于下面3中类型:
2.几条应用与所有预处理指令的规则:
3.带参数的宏:格式如下:#define 标识符(x1,x2,…,xn) 替换列表。
在宏的名字和左括号之间必须没有空格。如果有空格,预处理器会认为是在定义一个简单的宏。
4.宏定义包含两个运算符:#和##。编译器不会识别这两种运算符,相反,它们会在预处理是被执行。
#运算符将一个宏的参数转换为字符串字面量。它仅允许出现在带参数的宏的替换列表中。
##运算符可以将两个记号(例如标识符)“粘“在一起,成为一个记号。如果其中一个操作数是宏参数,“粘合“会在当形式参数被相应的实际参数替换后发生。
5.宏的通用属性
6.宏定义中添加圆括号的两条规则
7.在C语言中预定义了一些宏,这些宏主要是提供当前编译的信息。宏__LINE__和__STDC__是整型常量,其他3个宏是字符串字面量。
__LINE__ 被编译的文件中__LINE__所在的行号
__FILE__ 被编译的文件的名字
__DATE__ 编译的日期(格式“Mmm dd yyyy“)
__TIME__ 编译的时间(格式“hh:mm:ss“)
__STDC__ 如果编译器接受标准C,那么值为1
第15章编写大规模程序
1.
int i; /* declares i and defines it as well */
extern int i; /*declares i without defining it */
extern提示编译器变量i是在程序中的其他位置定义的(大多数可能是在不同的源文件中),因此不需要为i分配空间。顺便说一句,extern可以用于所有的类型的变量。在数组的声明中使用extern时,可以忽略数组的长度。
2.头文件存在是为了给编译器而不是为连接器提供信息。
第16章结构、联合和枚举
1.结构(structure)是可能具有不同类型的值(成员(member))的集合。
联合(union)和结构很类似,不同之处在于联合的成员共享同意存储空间。即:联合可以每次存储一个成员,但是无法同时存储全部成员。
枚举(enumeration)是一种整数类型,它的值由程序员命名。
2.编译器只为联合中最大的成员分配足够的内存空间。联合的成员在这个空间内彼此覆盖。
3.两个或多个枚举常量具有相同的值也是合法的。
4.通常可以选择使用标记或者用typedef來定义一周内跟特殊的结构类型的名字,但是,在结构有一个指向相同结构类型的成员时,要求使用结构标记。没有结构标记,就没有办法声明指向该结构类型的成员。
第17章指针的高级应用
1.内存分配函数:这些函数都是声明在
2.当为申请内存块而调用内存分配函数时,由于函数无法知道计划存储在内存块中的数据是什么类型的,所以不能返回普通的int型指针或char型指针或其他。取而代之的,函数会返回void*型的值。void*型的值是“通用“指针,本质上它知识内存地址。
3.C标准列出几条关于realloc函数的规则:
4.如果无法扩大内存块(因为内存块后边的字节已经用于其他目的),realloc函数会在别处分配新的内存块,然后把旧块中的内容复制到新块中。一旦realloc函数返回,一定要对指向内存块的所有指针进行更新,因为可能realloc函数移动到了其他地方的内存块。
5.free(p)函数会释放p指向的内存块,但是不会改变p本身。
6.当函数名后边没跟着圆括号时,C语言编译器会产生指向函数的指针來代替产生函数调用的代码。
第18章声明
1.声明说明符分为以下3大类:
2.C程序中的每个变量都具有3个性质:
3.变量的默认存储期限、作用域和链接都依赖于变量声明的位置:
4.auto存储类型支队属于块的变量有效。auto类型的变量具有自动存储期限、块的作用域,并且无链接。
5.static存储类型可以用于全部变量,而不需考虑变量声明所在的位置。但是块外部声明的变量和块内部声明的变量会有不同的效果。当用在块外部时,单词static说明变量具有内部链接。当用在块内部时,static把变量的存储期限从自动变成了静态的。
6.extern存储类型使几个源文件可以共享一个变量。extern声明中的变量始终具有静态存储期限。变量的作用域依赖于声明的位置。如果声明在块内部,那么变量具有块作用域;否则,变量具有文件作用域。
7.变量在程序中可以有多次声明,但只能有一次定义。
8.声明变量具有register存储类型就要编译器把变量存储在寄存器中,而不是像其他变量一样保留在内存中。register存储类型只对声明在块内的变量有效。register类型的变量具有自动存储期限、块的作用域,并且无链接。由于寄存器没有地址,所以对register类型变量使用取地址与算符&是非法的。
9.函数的存储类型:和变量的声明一样,函数的声明(和定义)可以包含存储类型,但是选项只有extern和static。在函数声明开始处的单词extern说明函数具有外部链接,也就是允许其他文件调用此函数。static说明内部链接,也就是说只能在定义函数的文件内部调用此函数。如果不指明函数的存储类型,那么会假设函数具有外部链接。
10.解释复杂声明有如下两条规则:
例如:
int *(*x[10])(void);
12.空值初始化式的额外规则:
13.变量的初始化值依赖于变量的存储期限
14.C语言规定对编译器而言每个数组的长度都必须是已知的。
第20章低级程序设计
1.移位运算符:
i<
2.可移植性技巧:为了更好的保留可移植性,最好仅对无符号数进行移位运算。
3.按位运算符
符号 | 含义 | 优先级 |
~ | 按位求反 | 最高级 |
& | 按位与 | |
^ | 按位异或 | |
| | 按位或 | 最低级 |
struct file_date {
unsigned int day:5;
unsigned int month:4;
unsigned int year:7;
};记住
6.长度为0的位域是给编译器一个信号,告诉编译器将下一个位域放在一个存储单元的起始位置。
第22章输入/输出
1.C程序中的流的访问是通过文件指针(file pointer)实现的。此指针拥有的类型为FILE*(在
2.在DOS操作系统中,文本文件和二进制文件之间存在两方面的差异:
与此相反的,UNIX操作系统对文本文件和二进制文件不进行区分。两者会以相同的方式进行存储。一个UNIX文本文件在每行的结尾只有单独一个换行符,而且没有特殊字符用来标记文件末尾。
3.当编写用来读或写文件的程序时,需要考虑是文本文件还是二进制文件。在无法确定文件是文本形式还是二进制形式时,安全的做法是把文件设定为二进制文件。
4.打开文件
FILE *fopen(const char *filename, const char *mode);
永远不能假设可以打开文件。为了确保不会返回空指针,需要始终测试fopen函数的返回值。
字符串 | 含义 |
“r“ | 打开文件用于读 |
“w“ | 打开文件用于写(文件不需要存在) |
“a“ | 打开文件用于追加(文件不需要存在) |
“r+“ | 打开文件用于读和写,从文件头开始 |
“w+“ | 打开文件用于读和写(如果文件存在就截去) |
"a+" | 打开文件用于读和写(如果文件存在就追加) |
字符串 | 含义 |
”rb“ | 打开文件用于读 |
”wb“ | 打开文件用于写(文件不需要存在) |
“ab“ | 打开文件用于追加(文件不需要存在) |
”r+b“或者“”rb+“ | 打开文件用于读和写,从文件头开始 |
”w+b“或者“wb+“ | 打开文件用于读和写(如果文件存在就截去) |
”a+b"或者“ab+“ | 打开文件用于读和写(如果文件存在就追加) |
5.关闭文件
int fclose(FILE *stream);
注:如果成功关闭了文件,那么fclose函数会返回零。否则,它将会返回错误代码EOF。
6.按照C程序员的编写习惯,通常不会把fopen函数的调用和fp的声明组合在一起使用。
7.为流附加文件
FIEL *freopen(const char *filename, const char *mode, FIEL*stream);
注:freopen 函数为已经打开的流附加上一个不同的文件。freopen 函数最常见的应用是把文件和其中一个标准流相关联,这些标准流包括:stdin、stdout或stderr。
freopen 函数通常返回的值是它的第三个实际参数(一个文件指针)。如果无法打开新的文件,那么freopen函数会返回空指针。(如果无法关闭旧的文件,那么freopen函数会忽略掉错误)。
8.文件缓冲
int fflush(FILE *stream);
void setbuf(FILE *stream, char *buffer);
void setvbuf(FILE *stream, char *buffer, int mode, size_t size);
int remove(const char *filename);
int rename(const char *old, const char *new);
注:remove函数和rename函数对文件名而不是文件指针进行处理。如果调用成功,两个函数都返回零。否则,都返回非零值。
要确信已经关闭了要移除的文件。如果打开了要换名的文件,那么一定要确保在调用rename函数之前此文件是关闭的。如果文件是打开的,则无法对文件进行换名。
10....printf类函数
int fprintf(FILE *stream, const char *format, ...);
int printf(const char *format, ...);
注:这两个函数的返回值是写入的字符数。若出错则返回一个负值。
11....scanf类函数
int fscanf(FILE *stream, const char *format, ...);
int scanf(const char *format, ...);
注:如果在能读取任何数据项之前发生输入失败,那么会返回EOF。
12.检测文件末尾和错误条件
void clearerr(FILE *stream);
int feof(FILE *stream);
int ferror(FILE *stream);
13.字符的输入/输出
注:这些函数用于文本流和二进制流是等效的。这些函数把字符作为int型而非char型來处理。这样做的原因之一就是由于输入函数是通过返回EOF來说明一个文件末尾(或错误)情况的,而EOF又是一个负的整型常量。
输出函数
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
虽然putc函数和fputc函数做的工作相同,但是putc函数经常作为宏來实现,而fputc函数则作为函数使用。putchar函数通常也作为宏來使用。
如果出现错误,那么上述这三中函数都会为流设置错误指示器并且返回EOF。否则,它们哦读会返回写入的字符。
输入函数
int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);
int ungetc(int c, FILE *stream);
注:当对文件进行读取时,始终要把fgetc函数、getc函数或getchar函数的返回值存储在int型的变量中,而不是char型的变量中。把char型变量与EOF进行比较可能会产生错误的结果。
14.行的输入/输出
输出函数
int fputs(const char *s, FILE *stream);
int puts(const char *s);
注:在雪乳字符串中的字符以后,puts函数总会添加一个换行符。而fputs函数不会自己写入换行符。当出现错误时,上面这两种函数都会返回EOF,否则,他们都返回一个非负的书。
输入函数
char *fgets(char *s, int n, FILE *stream);
char *gets(char *s);
15.块的输入/输出
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
int fgetpos(FILE *stream, fpos_t *position);
int fseek(FILE *stream, long int offset, int origin);
int fsetpos(FILE *stream, const fpos_t *pos);
long int ftell(FILE *stream);
void rewind(FILE *stream);