六:结构
1:结构体声明中,比如:
struct point{
int x;
int y;
};
struct后面的名字是可选的,称为结构标记。结构成员、结构标记和普通变量可以采用相同的名字,它们之间不会冲突,因为通过上下文分析,总可以对他们进行区分。
2:结构体的初始化,可以在定义的后面使用初值表进行。初值表中同每个成员对应的初值必须是常量表达式,例如: struct point maxpt = {320, 200};
3:结构体的合法操作只有几种:作为一个整体复制和赋值,通过&运算符取地址,访问其成员。其中,复制和赋值包括向函数传递参数,以及从函数返回值。结构体之间不可以进行比较。
4:在表达式(*pp).x中的圆括号是必须的,因为结构成员运算符”.”的优先级要比”*”额优先级高。表达式*pp.x等价于*(pp.x),因为x不是指针,所以这种表达式是非法的。
在所有运算符中,下面4个运算符的优先级最高:结构运算符”.”和”->”,用于函数调用的”()”以及用于下标的”[]”。它们的结合顺序是从左向右的。比如,对于下面的结构体:
struct {
int len;
char *str;
} *p;
表达式++p->len,将增加len的值,而不是增加p的值,这是因为该表达式等价于++(p->len);
表达式(++p)->len,将先执行p+1,然后在对len执行操作。
表达式(p++)->len,将先对len进行操作,然后执行p+1.
表达式*p->str,读取的是指针str所指向的对象的值,也就是str指向的字符串的第一个字符。
表达式*p->str++,先读取指针str指向的对象的值,然后在将str+=1;
表达式(*p->str)++,将指针str指向的对象的值加1;
表达式*p++->str,先读取指针str指向对象的值,然后在将p加1.
5:结构体数组的初始化可以如下:
struct key{
char *word;
int count;
}keytab[] = {
“auto”,0,
“break”,0,
“case”,0
};
更精确的做法是,将每一行的初值都括在花括号内,比如:
{{“auto”, 0},{“break”,0},{“case”,0}}
6:sizeof是一个编译时一元运算符,像下面的形式都是合法的:
sizeof 0; sizeof(0); sizeof(int);
但是,像sizeof int;就是非法的。
7:如果low和high都是指针,对于数组a[n]来说,low指向a[0],high指向a[n](最后一个元素的后一个元素),为了得到指向中间元素的指针mid,可以根据指针的减法运算得到:
mid = low + (high - low)/2;
8:向下面这样的声明称为自引用:
struct node{
int num;
struct node *next;
}
在结构体声明的内部,引用到了结构体本身的指针,这是合法的。偶尔也会见到自引用结构的一种变体,就是两个结构体相互引用:
struct t{
struct s *p;
}
struct s{
struct t*q;
}
9:联合可以在不同时刻,保存不同类型和长度的对象的变量,编译器负责跟踪对象的长度和对齐要求。比如:
union u_tag{
int ival;
float fval;
char *sval;
}u;
变量u必须足够大,以保存这3中类型中最大的一种。这些类型中的任何一种类型的对象,都可以赋值给u。但必须保证是一致的:读取的类型必须是最近一次存入的类型。程序员负责跟踪当前保存在联合中的类型。如果保存的类型与读取的类型不一致,其结果取决于具体的实现。
10:flags |= EXTERNAL|STATIC; 该语句将flags中的EXTERNAL和STATIC位置为1,下列语句:flags &= ~(EXTERNAL|STATIC); 该语句将flags中的相应位置为0.
11:C语言提供了,直接定义和访问一个字中的位字段的能力。这就是位字段,或简称字段。它是字中相邻为的集合。比如:
struct{
unsigned int is_keyword : 1;
unsigned int is_extern : 1;
unsigned int is_static : 1;
}flags;
这里定义了一个变量flags,它包含3个一位的字段。冒号后面的数字表示字段的宽度(二进制位数)。字段被声明为unsigned int 类型。可以这样的使用字段:
flags.is_extern= 0;
flags.is_keyword= 1;
flags.is_static= 1;
字段可以不命名,无名字段起填充作用。特殊宽度0表明强制在下一个字边界上对齐。
某些机器上,字段的分配是从字的左端到右端进行的,而某些机器上则相反。
字段不是数组,并且没有地址,因此对它们不能使用&运算符。比如&(flags.is_keyword)就是一种语法错误。
七:输入与输出
1:C标准库包含:输入输出函数、字符串处理函数、存储管理函数、数学函数等。ANSI标准精确定义了这些库函数,所以,在任何系统中都有这些函数的兼容形式。
2:最简单的输入函数是使用getchar函数从标准输入中,一般为键盘,一次读取一个字符:int getchar(void);getchar函数在每次调用时返回下一个输入字符。若遇到文件结尾,则返回EOF。符号常量EOF在头文件<stdio.h>中定义。
可以使用符号”<”实现输入重定向,他把键盘输入替换为文件输入,比如prog < infile,将使程序prog从输入文件infile,而不是键盘中读取字符。
输入也可以通过管道机制来自于另一个程序,比如:otherprog|prog,将运行两个程序otherprog和prog,并将程序otherprog的标准输出通过管道,重定向到prog的标准输入上。
3:int putchar(int)用于输出数据。putchar(c)将字符c输出到标准输出,默认情况下,标准输出为屏幕显示。如果没有出错,则putchar返回输出的字符;如果发生了错误,则返回EOF。
可以使用符号”>”将标准输出重定向到文件中,比如prog>outfile,将把prog的输出重定向到outfile中。
命令prog|antherprog,将把prog的标准输出,通过管道重定向到anotherprog的标准输入中。
4:格式化输出—printf
intprintf(char *format, arg1, arg2,...);该函数的返回值是打印的字符数。比如语句:
res = printf("hello,num is %d ", 12); 其中res为17,包括换行符。
格式字符串包含两种类型的对象:普通字符和转换说明。在输出时,普通字符将原样不同的复制到输出流中,而转换说明并不直接输出到输出流中,而是用于控制printf中参数的转换和打印。每个转换说明都由一个百分号字符“%”开始,并以一个转换字符结束。在字符“%”和转换字符中间可能依次包含下列组成部分:
负号,用于指定被转换的参数按照左对齐的形式输出。
数,用于指定最小字段宽度。转换后的参数将打印不小于最小字段宽度的字段。如果有必要,字段左边(如果使用左对齐的方式,则为右边)多余的字符位置用空格填充以保证最小字段宽。
小数点,用于将字段宽度和精度分开。
数,用于指定精度,即指定字符串中要打印的最大字符数、浮点数小数点后的位数、整型最少输出的数字数目。
字母h或l,字母h表示将整数作为short类型打印,字母l表示将整数作为long类型打印。
下表列出了所有的转换字符。如果%后面的字符不是一个转换说明,则该行为是未定义的。
在转换说明中,宽度或精度可以用星号*表示,这时,宽度或精度的值通过转换下一参数(必须为int类型)来计算。例如,为了从字符串s中打印最多max个字符,可以使用下列语句:printf(“%.*s ”, max, s); 更多的例子如下:
char *s = "0123456789";
printf("str is %.*s(end) ", 3,s); //输出:str is 012(end)
printf("str is %.*s(end) ", 0, s); //输出:str is (end)
printf("str is %.*s(end) ", 100, s); //输出:str is0123456789(end)
下表说明了在打印字符串”hello,world”(12个字符)时,根据不同的转换说明产生的不同结果。在每个字段的左边和右边加上冒号,这样可以清晰地表示出字段的宽度,对于字符串来说,宽度意味着最小宽度,精度意味着最大输出字符数,也就是说,宽度不会截断字符串,有可能会扩展输出字符串;精度不会扩展输出字符串,而有可能截断字符串。
printf(":%s: ",str); // :hello, world:
printf(":%10s: ",str); // :hello, world:
printf(":%.10s: ",str); // :hello, wor:
printf(":%-10s: ",str); // :hello, world:
printf(":%.15s: ",str); // :hello, world:
printf(":%-15s: ",str); // :hello, world :
printf(":%15.10s: ", str); // : hello, wor:
printf(":%-15.10s: ",str); // :hello, wor :
下表说明了,宽度和精度,对于整型值12345的作用,可见,对于整型来说,宽度意味着最小宽度,精度意味着最少输出的数字数目。也就是说宽度不会截断数字,有可能会扩展输出字符串;精度也不会截断数字,有可能会为输出数字填充0。
int b = 12345;
printf(":%d: ",b); // :12345:
printf(":%3d: ",b); // :12345:
printf(":%10d: ",b); // : 12345:
printf(":%.3d: ",b); // :12345:
printf(":%.10d: ",b); // :0000012345:
printf(":%.0d: ",b); // :12345:
printf(":%-3d: ",b); // :12345:
printf(":%-15.10d: ",b); // :0000012345 :
下表说明了,宽度和精度,对于浮点数1.2345678的作用,可见,对于浮点数来说,宽度意味着最小宽度,精度意味着精确的数字数目。也就是说宽度不会截断数字,有可能会扩展输出字符串;精度就是小数点后的位数。
double d =1.2345678;
printf(":%f: ",d); // :1.234568:
printf(":%3f: ",d); // :1.234568:
printf(":%10f: ",d); // : 1.234568:
printf(":%.3f: ",d); // :1.235:
printf(":%.10f: ",d); // :1.2345678000:
printf(":%.0f: ",d); // :1:
printf(":%-3f: ",d); // :1.234568:
printf(":%-15.10f: ",d); // :1.2345678000 :
综上所述,宽度对于字符串、整型、浮点型来说,都不会截断输出的字符串,而有可能会以空格扩展输出的字符串;
精度对于字符串来说,不会扩展字符串,而有可能会截断字符串。精度对于整形来说,不会截断整型,而有可能会以0填充数字;精度对于浮点数来说,就是精确的小数点位数。
函数printf使用第一个参数判断后面参数的个数和类型。如果参数的个数不够或类型错误,则将得到错误的结果。
sprintf与printf类似,但她将输出保存到一个字符串中,而不是标准输出中。
5:变长参数表
printf的声明为: int printf(char *fmt, ...);
其中,省略号表示参数表中参数的数量和类型是可变的。标准头文件<stdarg.h>中包含一组宏定义,它们对如何遍历参数表进行了定义。该头文件的实现因不同的机器而不同,但提供的接口是一致的。
宏va_list类型用于声明一个变量,该变量将依次引用各参数;
宏va_start将ap初始化为指向第一个无名参数的指针;
宏va_arg,将返回一个参数,并将ap指向下一个参数。va_arg使用一个类型名来决定返回的对象类型、指针移动的步长。
宏va_end,完成一些必要的清理工作。
利用这些宏,实现简单的printf例子如下:
voidminprintf(char *fmt, ...)
{
va_list ap;
char *p, *sval;
int ival;
double dval;
va_start(ap,fmt);
for(p = fmt; *p; p++)
{
if(*p != '%')
{
putchar(*p);
continue;
}
switch(*++p)
{
case 'd':
ival = va_arg(ap, int);
printf("%d",ival);
break;
case 'f':
dval = va_arg(ap, double);
printf("%f",dval);
break;
case 's':
for(sval =va_arg(ap, char*); *sval; sval++)
{
putchar(*sval);
}
break;
default:
putchar(*p);
break;
}
}
va_end(ap);
}
6:格式化输入—scanf函数
scanf函数声明:int scanf(char *format, ...);
scanf中,除了format之外,其他所有的参数必须是指针,用于指定经格式转换后的相应输入保存的位置。
当scanf扫描完其格式串(输入字符串中),或者碰到某些输人无法与格式控制说明匹配的情况时,该函数将终止。同时,成功匹配并赋值的输人项的个数将作为函数值返回。如果到达文件的结尾,该函数将返回EOF。下一次调用scanf函数的时候,将在输入字符串上,从上一次转换的最后一个字符的下一个字符开始继续搜索。
格式串通常都包含转换说明,用于控制输入字符串的转换。格式串可能包含下列部分:
空格或制表符,在处理过程中将被忽略;
普通字符(不包括%〕,用于匹配输人流中下一个非空白符字符;
转换说明,依次由一个%、一个可选的赋值禁止字符*、一个可选的数值(指定最大字段 宽度)、一个可选的h,l或L字符以及一个转换字符组成。
如果转换说明中有赋值禁止字符*,则跳过该输入字段,不进行赋值。比如:
res = scanf("%d %*d %d",&a, &b, &c);
printf("a, b, c is %d, %d,%d ", a, b, c);
printf("res is %d ", res);
输入“1 2 3”,得到输出结果为:
a, b, c is 1, 3, -1077261788
res is 2
输人字段定义为一个不包括空白符的字符串,其边界定义为到下一个空白符或达到指定的字段宽度。空白符包括空格符、横向制表符、换行符、回车符、纵向制表符以及换页符。包含宽度的例子如下:
res = scanf("%2d %2d %2d",&a, &b, &c);
printf("a, b, c is %d, %d,%d ", a, b, c);
printf("res is %d ", res);
输入“1234567”,得到结果:
a, b, c is 12, 34, 56
res is 3
输入“1 2 3456”,得到结果:
a, b, c is 1, 2, 34
res is 3
转换字符如下表:
转换说明d,i,o,u及x的前面可以加上字母h或l。前缀h表明参数表的相应参数是一个指向short类型而非int类型的指针,前缀l表明参数表的相应参数是一个指向long类型的指针。类似地,转换说明e,f和g的前面也可以加上前缀l,它表明参数表的相应参数是一个指向double类型而非float类型的指针。
字符字面值也可以出现在scanf的格式串中,它们必须与输入中相同的字符匹配。因此,我们可以使用下列scanf语句读入形如mm/dd/yy的日期数据:
int day, month,year;
scanf(“%d/%d/%d”,&month, &day, &year);
如果要读取格式不固定的输人,最好每次读人一行,然后再用sscanf将合适的格式分离出来读人。例如,假定我们需要读取一些包含日期数据的输人行,可以这样编写程序:
while (getline(line, sizeof(line)) >0) {
if (sscanf(line, "%d %s%d", &day, monthname, &year) == 3)
printf("valid:%s ", line); /* 25 Dec 1988 form */
else if (sscanf(line,"%d/%d/%d", &month, &day, &year) == 3)
printf("valid:%s ", line); /* mm/dd/yy form */
else
printf("invalid:%s ", line); /* invalid form */
}
7:库函数fopen可以打开文件,并返回一个随后可用于文件读写操作的指针。该指针称为文件指针,它指向一个包含文件信息的结构。这些信息包括:缓冲区的位置、缓冲区中当前字符的位置、文件的读或写状态、是否出错或是否已经到达文件结尾等等。在<stdio.h>中定义了一个包含这些信息的结构FILE。在程序中可以这样使用:
FILE *fp;
FILE *fopen(char*name, char *mode);
如果打开一个不存在的文件用于写或追加,该文件将被创建(如果可能的话)。当以写方式打开一个已存在的文件时,该文件原来的内容将被覆盖。但是,如果以追加方式打开一个文件,则该文件原来的内容将保留不变。读一个不存在的文件会导致错误,其他一些操作也可能导致错误,比如试图读取一个无读取权限的文件。如果发生错误,fopen将返回NULL。
启动一个C语言程序时,操作系统环境负责打开3个文件,并将这3个文件的指针提供给该程序。这3个文件分别是标准输人、标准输出和标准错误,相应的文件指针分别为stdin,stdout和stderr,它们在<stdio.h>中声明。在大多数环境中,stdin指向键盘,而stdout和stderr指向显示器。stdin和stdout可以被重定向到文件或管道。
函数 int fclose(FILF*fp);它执行和fopen相反的操作,它释放文件指针以供其他文件使用。因为大多数操作系统都限制了一个程序可以同时打开的文件数,所以,当文件指针不再需要时就应该释放,这是一个好的编程习惯。对输出文件执行fclose还有另外一个原因:它将把缓冲区中由putc函数正在收集的输出写到文件。当程序正常终止时,程序会自动为每个打开的文件调用fclose函数。
如果流fp中出现错误,则函数ferror返回一个非0值。int ferror(FILE *fp);函数feof与ferror类似。如果指定的文件到达文件结尾,它将返回一个非0值:int feof(FILE *fp);
8:内存管理(stdlib.h)
void*calloc(size_t nobj, size_t size)
calloc函数为由nobj个长度为size的对象组成的数组分配内存,并返回指向分配区域的指针。若无法满足要求,则返回NULL。该空间被初始化为0。
void*malloc(size_t size)
mallflc函数为长度为size的对象分配内存,并返回指向分配区域的指针;若无法满足要求,则返回NULL。该函数不对分配的内存区域进行初始化。
void *realloc(void*p, size_t size)
realloc函数将p指向的对象的长度修改为size个字节。如果新分配的内存比原内存大,则原内存的内容保持不变,增加的空间不进行初始化。如果新分配的内存比原内存小,则新分配内存单元不被初始化。realloc函数返回指向新分配空间的指针,若无法满足要求,则返回NULL,在这种情况下,原指针p指向的单元内容保持不变。
void free(void*p)
free函数释放p指向的内存空间。当p的值为NULL时,该函数不执行任何操作。p必须指向先前使用动态分配函数mallac, realloc或calloc分配的空间。
1:空白符包括空格、横向制表符、纵向制表符、换行符、换页符。
2:注释以/*开始,以*/结束。注释不能嵌套。
3:指向任何对象的指针都可以转换为void *类型,且不会丢失信息。如果将结果再次转换为初始指针类型,则可以恢复初始指针。
4:运算符的优先级和结合性有明确的规定,但是,除少数例外情况外,表达式的求值次序没有定义。也就是说,除非运算符的定义保证了其操作数按某一特定顺序求值,否则,具休的实现可以自由选择任一求值次序,甚至可以交换求值次序。
5:在第1版中,通过指向函数的指针调用函数时必须有一个显式的*运算符。ANSI标准允许现有的一些编译器用同样的语法进行函数调用和通过指向函数的指针进行函数调用。旧的语法仍然有效。
6:指向数组中某个对象的指针可以和一个任何整型的值相加,后者将通过乘以所指对象的长度被转换为地址偏移量。相加得到的和是一个指针,它与初始指针具有相同的类型,并指向同一数组中的另一个对象,此对象与初始对象之间具有一定的偏移量。因此,如果P是一个指向数组中某个对象的指针,则表达式P+1是指向数组中下一个对象的指针。如果相加所得的和对应的指针不在数组的范围内,且不是数组末尾元素后的第一个位置,则结果没有定义。
如果指向同一类型的两个指针相减,则结果是一个带符号整型数,表示它们指向的对象之间的偏移量。相邻对象间的偏移量为1。比如:
p = a;
q = &a[3];
printf("q - p is %d ", q-p); // 3
可以对指向同一类型的对象的指针进行比较,其结果依赖于所指对象在地址空间中的相对位置。指针比较只对相同对象才有意义:
如果两个指针指向同一个简单对象,则相等;
如果指针指向同一个结构的不同成员,则指向结构中后声明的成员的指针较大;如果指针指向同一个联合的不同成员,则相等;
如果指针指向一个数组的不同成员,则它们之间的比较等价于对应下标之间的比较。如果指针P指向数组的最后一个成员,尽管P+1已指向数组的界外,但P+1仍比P大;
其他情况下指针的比较没有定义。
7:由逗号分隔的两个表达式的求值次序为从左到右。右操作数的类型和值就是结果的类型和值。