1.在声明一个指针时,一定要把*靠近变量,而不要靠近类型,如int *a;
2. 一个变量的作为右值来用时就是分配给这个变量的内存位置所存储的数值。而作为左值(如果可以的话),就是内存位置。
解释:如int a; a本身是一个地址(也可以说是给a分配的内存位置),但是a的值不是一个地址,而是这个地址的内容(a表示的内存位置所存储的数值)
*&a=25,&操作符产生变量a的地址,它是一个指针常量。*操作符访问其操作数所表示的地址,这里操作数是a,所以25就存储在a中。(如*c访问c所指向的位置(地址))
3.链接属性:处理在不同文件中出现的相同的标识符(名字)
值得注意的是:1)所有的函数,所有的global变量,在缺省(无static显式声明)情况下,皆为external链接属性(这个是肯定的哇)。
2) 除了external链接属性,所有的标识符在缺省情况下,都是none链接属性(类似于auto变量(局部变量)的存储类型)。
3)通过以上两条可以得出结论,只有显式的声明static,才能让标识符变为internal链接属性---------此时相应的变量和函数是对外文件隐藏的.且static只有对缺省链接属性为external的声明才有改变链接属性的效果(因为对none链接属性而言,加上static是完全不同的含义-----局部静态变量(存储类型,而非链接属性))。
4. if( strlen(x) >= strlen(y) ) ...
if( strlen(x) - strlen(y) >=0) ...
这两个表达式是不相等的,原因在于strlen返回一个无符号整型数,而两个无符号整型数的差值必然是大于零的。如printf("%u",1-2);结果不是-1,而是-1的32位的补数。
5.使用strcpy函数,要确保目标内存是充足的,否则源字符串将覆盖目标内存后面的内存。
6.if(strcmp(a.b)) ,用这个表达式跟我们的预想是相反的,因为a若等于b,strcmp返回的是0,而用if表达式的本意是如果相等则结果为真,可惜的是相等的结果是返回“假”!!
所以不要偷懒,要把它跟零比较!!!
7.对于长度受限的字符串函数,如char *strncpy( char *dst, char const *src, size_t len)
这个函数的结果并不会以NULL字节结尾(当然strlen(src)<len时 dst 就用额外的NULL字节填充到len长度的情况除外)
所以有如下语句:
char buffer[BSIZE];
...
strncpy( buffer, name, BSIZE);
buffer[BSIZE-1] = ' \0 '; //当name可完全容纳于buffer中时(即name长度小于BSIZE),这条赋值语句是没有任何效果的(name本身的NULL字节也被复制到buffer中)。
函数char *strncat( char *dst, char const *src, size_t len):总是在结果字符串后面自动添加一个NULL字节,而且它不会对目标数组用NULL字节进行填充;
函数int strncpy( char const *s1, char const *s2, size_t len):用于比较最多len个字节的情况。
8. strrstr函数的实现(返回子串最后一次出现的位置,库函数中没有)——注意last的用法很普遍
#include<string.h>
char *strrstr(char const*s1,char const*s2)
{
register char *last;
register char *current;
last=NULL; //把指针初始化为我们已经找到的前一次的匹配位置
if(*s2!='\0'){
current=strstr(s1,s2); //查找第一次出现的位置
while( current!=NULL){
last=current;
current=strstr(last+1,s2); //巧妙的方法(last+1)—— last+strlen(s2)也是阔以的哇,效率应该更高
}
}
return last; //如果s2为空串,返回值为NULL
}
9.strtok函数
从字符串中隔离各个单独的成为标记(token,一个个字符串或其他)的部分,并丢弃分隔符。
函数原型:char *strtok( char *str, char const *sep);
其中,sep参数是分隔符的字符集合。str指定一个字符串,它包含一个或多个由sep字符串中一个或多个分隔符分隔的标记。
返回值:找到str的下一个标记,并将其用NULL结尾,然后返回一个指向这个标记的指针。
1)str!=NULL 函数将找到字符串的第一个标记,同时保存它在字符串中的位置;
2)str==NULL 函数就在同一个字符串中从这个被保存的位置开始像前面一样查找下一个标记;
3)如果字符串内不存在更多的标记,函数返回一个NULL指针。
这个函数被重复调用时,就把第一个参数设置为NULL。
举例:
/****从一个字符数组中提取空白字符作为分隔符的标记
*****并把它们(这些被分隔的标记)打印出来**********
*************************************************/
#include<stdio.h>
#include<string.h>
void print_tokens(char *lines)
{
static char whitespace[]="\t\f\r\v\n";
char *token;
for( token=strtok(line,whitespace);token!=NULL;token=strtok(NULL,whitespace))//只要匹配分隔符集合中的任何一个分隔符,就寻找标记成功
printf("Next token is %s\n",token);
}
10.mem×××:对任意的字节序列进行内存处理,即便中间有NULL字节
void *memcpy(void *dst, void const *src, size_t length);
当dst和src都是整型数组怎么办?——memcpy( a, b, sizeof(b));或者memcpy( a, b, count*sizeof(b[0] ));
void *memmove(void *dst, void const *src, size_t length); //区别于上一个函数,这个函数有中转内存,所以可用于处理dst和src的内存重叠的情况
如,memmove( x, x+1, (count-1)*sizeof ( x[0] ) ); //把数组元素向前挪一个元素的位置,即x[1]挪到x[0],x[2]挪到x[1],以此类推~~~
void *memcmp(void const*a, void const *b, size_t length);
void *memchr(void const*a, int ch, size_t length); //查找指定的字符(可越过NULL字节进行查找,而不会像字符串函数那样遇NULL字节则停止查找)
void *memset(void const*a,int ch, size_t length); //置位
11.结构的自引用
struct SELF_REF1{
int a;
struct SELF_REF1 b;
int c;
};
//注意这样的自引用是错误的,这将是一个永无止尽的递归调用。
正确的应该如下所示:
struct SELF_REF2{
int a;
struct SELF_REF2 *b;
int c;
};
//编译器在结构的长度确定之前就已经知道指针的长度(统一都是等于机器的字长,跟指针类型无关),所以合法。
//常用于指向链表的下一元素或树的下一个分枝。
12.用typedef创建类型名时注意:
typedef struct{
int a;
struct SELF_REF3 *b;
int c;
} SELF_REF3;
//结果将导致失败,因为类型名知道声明的末尾才定义,所以在结构声明的内部它尚未定义。
//正确的如下所示:
typedef struct SELF_REF3_TAG{
int a;
struct SELF_REF3_TAG *b;
int c;
} SELF_REF3;
//即用结构原本的标签名才能成功创建类型名。
13.由于机器的内存对齐原则,可以通过如下原则来使内存的利用效率最大化:使那些对内存边界最严格的成员首先出现,使边界要求最弱的成员最弱出现。
但是,一般情况下却不会这么做,原因在于:我们想把那些相关的结构成员存储在一起,提高程序的可维护性和可维护性。
当程序需要创建成百上千的结构时,减少内存浪费的要求要大于程序的可读性,在这种情况下,必要的注释可以避免可读性方面的损失。
14.位段
其声明和任何普通的结构成员声明相同。但有两个例外:1)位段成员必须声明为int, unsigned int , signed int 类型(最好只用后两者,如果用int,其解释由编译器决定,移植性差)。2)成员名的后面是一个冒号和一个整数,这个整数指定该位段所占用的位的数目。
15.关于动态内存分配的知识点熟练掌握程序11.4.
学会固定增值的用法:
例题3. 编写一个函数,从标准输入读取一个字符串(遇换行符或者EOF标志结束读取),把字符串复制到动态分配的内存中,并返回该字符串的拷贝。这个函数不应该对输入的长度做任何限制。
代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define DELTA 3 //固定增值
char *readstring()
{
char *buffer=NULL; //存储申请空间的返回值
int buffer_size=0; //buffer_size用作扩充申请内存
int len=0; //记录读取字符的实时长度
char *bp=buffer; //<span style="font-family: Arial, Helvetica, sans-serif;">bp始终指向下一个空闲字节处</span>
int ch;
while((ch=getchar())!='\n'&&ch!=EOF){
if(len>=buffer_size){ //如果申请的空间不够,就使申请的内存增加一个固定值.
buffer_size+=DELTA;
buffer=(char*)realloc(buffer,buffer_size); //buffer始终指向申请的总空间的开头,而非新增加空间的空闲处
bp=buffer+len; //指向扩充后的内存的第一个空闲字节处
}
*bp++=ch; //bp始终指向下一个空闲字节处
len++;
}
*bp='\0'; //字符串终止符
// bp=(char*)malloc(len); //使bp指向另外申请的一块足量的总空间的开头(原本的字符都是存储在buffer内存中的)
// strcpy(bp,buffer);
// return bp;
return buffer; //用这一句代替上面的三条语句我觉得更好
}
int main()
{
char *s=readstring();
printf("%s\n",s);
return 0;
}
16.链表的插入操作
/******************************************************/
/******************************************************/
/********************单链表的插入操作******************/
#include<stdio.h>
#include<stlib.h>
#define FALSE 0
#define TRUE 1
typedef struct NODE{
struct NODE *link;
int value;
}Node;
int sll_insert(register Node **linkp,int new_value )
{
Node *current;
Node *new;
while((current=*linkp)!=NULL && current->value<new_value)
linkp=¤t->link;
new=(Node*)malloc(size(Node))
if(new==NULL)
return FALSE;
new->value=new_value;
new->link=current;
*linkp=new; // 实质是current->link = new;
return TURE;
}
17. 在C语言中,声明是以推论的形式进行分析的。如,int *a; 把表达式*a声明为一个整型,我们必须推论出a是一个指向整型的指针。复杂的声明分析同样遵循这一原则。
分析得到声明结果的形式应该是这样的:f是一个......(函数,指向函数的指针),返回值(如果有的话)是......。
值得注意的是,1)关于返回值的修饰词:凡是带()的声明,必是函数的声明,那么就会有返回值(returning)这样的字眼(UNIX的cdecl函数的依据所在,TCPL);
返回值的修饰词是由落单在所有括号外的*及数据类型决定的;
2)关于如何确定 f 的修饰词:看*是跟谁结合——如果跟f 结合,即(*f),说明f 是一个指针,而且指针都是有指向的,这个指向就是括号后面的东西,如(*f)( ),说明f是一个指针,这个指针指向的是一个函数。再如(*f[ ] )( ),下标优先级高于*,所以f 首先是一个数组,然后这个数组的元素都是指针,每个指针指向的都是一个函数;如果和前面的类型名结合,说明返回值是一个指针(如果是函数的话)。
如:
int *f( ); 解析: 有(),故有返回值;* 在所有括号外面,所以是返回值修饰词的一部分,加上前面的数据类型int,可以得出返回值类型是一个指向整型的指针,完整的声明表达如下:f是一个函数(而非一个指针),这个函数的返回值是一个指向整型数值的指针。
int (*f)( ); 解析: 有(),故有返回值;所有括号外面没有*,所以返回值类型是int。f是一个指针,但它指向的不是返回值,而是函数本身。完整的声明表达如下:f是一个指向函数的指针(而非一个函数),它指向的函数返回值是一个整型值。
int *(*f)( ); 解析: 有(),故有返回值;* 在所有括号外面,所以是返回值修饰词的一部分,加上前面的数据类型int,可以得出返回值类型是一个指向整型的指针。f是一个指针,但它指向的是函数。完整的声明表述如下:f是一个指向函数的指针,它指向的函数返回值是一个指向整型数值的指针。
int (*f[ ])( ); 解析: 有(),故有返回值;所有括号外面没有*,所以返回值类型是int。f是一个数组,这个数组的元素是指针,这些指针指向的是函数。完整的声明表述如下:f是一个指向函数的指针的数组,它的元素所指向的函数的返回值是一个整型值。
int *(*f[ ])( ); 解析: 有(),故有返回值;* 在所有括号外面,所以是返回值修饰词的一部分,加上前面的数据类型int,可以得出函数的返回值类型是一个指向整型的指针。f是一个数组,这个数组的元素是指针,这些指针指向的是函数。完整的声明表述如下:f是一个指向函数的指针的数组,它的元素所指向的函数的返回值是一个指向整型值的指针。
在理解上述规律后,才有可能理解TCPL上的源码。
18.使用函数指针,旨在写出通用性更强的代码。
int f( int ); //函数指针初始化必须有其所指向函数的原型,否则编译器无法检查f的类型是否与pf所指向的类型一致。
int (*pf)(int)=&f; //取地址符是可选的,因为函数名本身就是一个指针,指向函数所在内存的起始位置。
调用时,
int ans; //一下三种调用是等价的
ans = f(25);
ans = (*pf)(25); //实质上,*pf=f(因为pf=&f)
ans = pf(25); // pf = &f, & 可选,则 pf = f
19. 一个神秘函数教我们如何使用字符串常量
我们引用的实际是指向字符串常量开头字符的指针,该指针的移动就意味着读字符串的部分,但我们无法给该指针赋值,即可读不可写。
/**************************************/
/**神秘函数,参数是一个0~100之间的数**/
#include<stdio.h>
void mystery( int n )
{
n+=5;
n/=10;
printf( “%s\n” , "**********" +10-n); //参数为0,就打印0个星号,参数100,就打印10个星号,位于0~100之间打印0~10个星号
}
20.副作用:表达式在求值时产生的不可逆转的永久性效果。
如x+1,,可以重复执行无数次,且每次获得的结果是一样的,所以这个表达式不具有副作用;再如 x++,它就具有副作用:x的值加1.当这个表达式下一次执行时,它将产生一个不同的结果。
有些宏参数是有副作用的:
1)P284
#define MAX( a , b ) ((a)>(b) ? (a) : (b) )
...
x=5;
y=8;
z=MAX( x++, y++);
printf(" x = %d, y = %d, z = %d\n" , x , y , z ); //结果是x=6,y=10,z=9
2) getchar( );
读取字符的宏也是具有副作用的,他将消耗输入的一个字符,所以该宏后续调用将得到不同的字符。
如果我们确定宏参数具有副作用,如果需要的话,可以在使用宏之前先把参数存储在临时变量中。
21.使用宏的语法和使用函数的语法是完全一样的,所以语言本身并不能帮助我们区分这两者。下表是宏和函数的一些区别:
22. #undef name
如果一个现存的名字需要被重新定义,那么它的旧定义首先要使用#undef 解除。
23.scanf的格式代码前常添加诸如h,l,L等限定符以指定(输入)参数的宽度。具体见 P309表格。
如果整型参数比缺省的整型值更长或者更短时,我们常常用限定符来避免错误的发生。这些错误可能是:导致一个较长变量只有部分被初始化,或者一个较短变量的临近变量也被修改。
如%d,要求输入的参数必须为整型(int,缺省时一般为32位(假设)),但是如果实际输入(比如键盘输入)的参数是long型,编译器将会截取int的位数存储到相应内存,如果输入的实际参数是short型,则就要对它进行扩充。
因此,用 h 和 l 限定符匹配对应实际输入的short或long型整数,可以让程序更具移植性。