c语言学习札记

c语言学习笔记

c语言学习笔记
2010年12月12日
  C语言学习笔记,还不错,来自一些经典的书籍哦.
  1.longjmp()和setjmp()是用于实现程序执行中的远程跳转。当你在程序中调用setjmp()时,程序当前状态将被保存到一个jmp_buf类型的结构中。此后,你可以通过调用longjmp()函数恢复到调用setjmp()时的程序状态。与goto语句不同,longjmp()和setjmp()函数实现的跳转不一定在同一个函数内。然而,使用这两个函数有一个很大的缺陷:当程序恢复到它原来所保存的状态时,它将失去对所有在setjmp()和longjmp()之间动态分配的内存的控制,也就是说这将浪费所有在setjmp()和longjmp()之间用malloc()和calloc()分配所得的内存。(所以请尽量避免使用setjmp()和longjmp()函数,这是不良习惯),示范如下:  #include  #include  jmp_buf saved_state; void call_longjmp(void); int main() { int ret_code; printf("The current state of the program is being saved..\n"); ret_code=setjmp(saved_state); if(ret_code==1) { printf("The longjmp function has been called.\n"); printf("The program's previous state has been restored.\n"); exit(0); } printf("I am about to call longjmp and\n"); printf("return to the previous program state..\n"); call_longjmp(); return 0; } void call_longjmp(void) { longjmp(saved_state,1); }  2.结构体可以作为左值. 你可以把一个结构变量赋给另一个同类型的结构变量,例如: typedef struct_name { char last_name[25]; char first_name[15]; char middle_init[12]; }NAME; ……NAME my_name,your_name; …your_name=my_name;….  在上例中,结构变量my_name的全部内容被拷贝到结构变量your_name中,其作用和下述语句相同:memcpy(your_name,my_name,sizeof(your_name)。
  3.常量字符串不可以赋值,如: char *achar="yes"; *(achar+1)='o'; //错了,不能给常量字符串这样赋值, 但是可以这样: char achar[]="yes"; *(achar+1)='o'; achar++;//错了不能改变字符数组名的值  4.有关变量注意:在函数外部声明为static的变量的作用域为从定义点到该文件尾部;在函数内部声明为static的变量的作用域为从定义点到该局部程序块尾部。如果声明了一个static变量,就必须在同一个文件中定义该变量(因为存储类型修饰符static和extern是互斥的)。你可以在头文件中定义一个static变量,但这会使包含该头文件的源文件都得到该变量的一份私有拷贝,而这通常不是你想得到的结果。(将导致这个变量不会在多个文件*享,而是每个文件都有一个私有的static变量)。变量的内存位置:(1)在函数外部定义的变量(全局变量或静态外部变量)和在函数内部定义的static变量,其生存期就是程序运行的全过程,这些变量被存储在数据段(data segment)中。(2)在函数内部定义的auto变量的生存期从程序开始执行其所在的程序块代码时开始,到程序离开该程序块时为止。作为函数参数的变量只在调用该函数期间存在。这些变量被存储在栈(stack)中。(3)如果把调用malloc()函数的结果赋给一个指针,那么这个指针变量将包含一块动态分配的内存地址,这块内存位于一段名为堆(heap)的内存空间中。堆可以和数据段或栈共用一个内存段,也可以有它自己的内存段,这完全取决于编译选项和操作系统。变量必须初始化吗:在函数外部定义的变量或者在函数内部用static关键字定义的变量(也就是上述在数据段中的那些变量)在没有明确被程序初始化之前都已经被系统初始化为0了。而在函数内部或程序块内部定义的不带static关键字的变量都是自动变量,它们具有未定义的值,所以请在使用它们之前必须保证先给它们赋值。调用malloc()函数从堆中分配到的空间也包含未定义的数据,在使用它之前必须先初始化,但调用calloc()函数分配到的空间在分配时就已经被初始化为0了。可以在头文件中声明或定义变量吗:被多个文件存取的全局变量可以并且应该在一个头文件中声明,并且必须在一个源文件中定义。变量不应该在头文件中定义,因为一个头文件可能被多个源文件包含,而这将导致变量被多次定义。有关变量的最大值:要判断某种特定类型可以容纳的最大值或最小值,一种简便的方法是使用ANSI标准头文件limits.h中的预定义值:CHAR_BIT,CHAR_MAX,CHAR_MIN,INT_MAX,INT_MINLONG_MAX,LONG_MIN等等。有关浮点数比较:不要用==,!=之类的,因为浮点数在计算机中的表示不精确,应该用之类的。
  5.有关constconst指针,它所指向的数据不能被修改。如: const char *str="hello"; char c=*str; str++; *str='a'; //非法 str[1]='b';//非法  在声明函数参数时,常常要使用const指针。例如,一个计算字符串长度的函数不必改变字符串内容,它可以写成这样: my_strlen(const char *str) { int count=0; while(*str++) { count++; } return count; }  注意,如果有必要,一个非const指针可以被隐式地转换为const指针,但一个const指针不能被转换为非const指针。这就是说,在调用my_strlen()时,它地参数既可以是一个const指针,也可以是一个非const指针。const变量用const修饰符修饰的变量不能被修改,如:int const me=20;const int you=30;me=40;//非法you=50;//非法而const char * const btr="yes",表示btr指向的数据不能改变并且btr本身也不能被改变。(3)使用const的好处:编译器能对程序进行优化并能保证不会因为程序员的疏忽而改变变量的值。(与define定义变量的区别,const变量可以是任何类型,此外const变量是真正的变量)
  6.registerregister修饰符暗示编译程序相应的变量将被频繁使用,如果可能的话,应将其保存在CPU的寄存器中,以加快存取速度。但是,使用register有几点限制:首先register变量必须是能被CPU寄存器所接受的类型。通常是一个单个的值,并且长度应小于或等于整型的长度。有些寄存器也可以存取浮点数。其次,register变量可能不存放在内存中,所以不能用取址运算符"&"来获取register变量的地址。再次,寄存器的数量是有限的,而且在有时候,把变量保存在寄存器中反而会降低运行速度,因为被占用的寄存器不能再用于其他目的了,或者变量被使用的次数不够多。所以,对现有的大多数编译程序来说,永远不要使用register修饰符。编译器会自动优化或者会忽略register修饰符的。
  7.有关volatile修饰符.volatile修饰符告诉编译程序不要对该变量所参与的操作进行某些优化。两种情况下用它:第一种情况涉及到内存映射硬件(memory-mapped hardwar,如图形适配器,这类设备对计算机来说就好像是内存的一部分一样),第二种情况涉及共享内存。好的编译程序能进行一种被称为"冗余装入和存储的删除"的优化,即编译程序会在程序中寻找并删除两类代码:一类是可以删去的内存装入数据的指令,因为相应的数据已经被存放在寄存器中;另一种是可以删去的将数据存入内存的指令,因为相应的数据在再次改变之前可以一直保留在寄存器中。如果一个指针变量指向普通内存以外的位置,如指向一个外围设备的内存映射端口,那么冗余装入和存储的优化对它来说可能是有害的。例如,为了调整某个操作的时间,可能会用到下述函数: time_t time_addition(volatile const struct timer *t,int a) { int n; int x; time_t then; for(n=0;nvalue-then; }  在上述函数中,变量t->value实际上是一个硬件计数器,其值随时间增加。该函数记录一段执行期间t->value所增加的值。如果不使用volatile修饰符,一个聪明的编译程序可能会认为t->value在该函数执行期间不会改变,因为函数内没有明确地改变t->value的语句。这样,编译程序就会认为没有必要再次从内存中读入t->value并将其减去then,因为答案永远是0。因此,编译程序可能会对该函数进行"优化",结果使得该函数的返回值永远是0。如果一个指针变量指向共享内存中的数据,那么冗余装入和存储的优化对它来说也可能有害。共享内存用来在几个程序之间互相通讯,如果从共享内存装入数据或把数据存入共享内存的代码被编译程序优化掉了,程序之间的通讯就会受到影响。注意:一个变量可以同时被声明为const和volatile,因为,const修饰符的含义是变量的值不能被使用了const修饰符的那段代码修改,但这并不意味着它不能被这段代码以外的其它手段修改。如上例所示。再有,不应该对用const或volatile声明了的对象进行类型强制转换,否则程序就不能正确运行了。 
  8.绝不能单凭errno的值来判断是否发生了错误,而应该根据函数的返回值来判断是否应该检查errno的值。
  9.怎样恢复一个重定向了的标准流。dup()函数可以复制一个文件句柄,fdopen()函数可以打开一个已用dup()函数复制了的流。如下: #include  int main() { int orig_stdout; orig_stdout=dup(fileno(stdout)); printf("Writing to original stdout…\n"); freopen("redix.txt","w",stdout); printf("Writing to redirected stdout…\n"); fclose(stdout);fdopen(orig_stdout,"w"); printf("I'm back writing to the original stdout.\n"); }  10.用流函数还是低级函数流函数(如fread(),fwrite())带缓冲区,在读写文本或二进制文件时效率更高。通常用它们存取非共享文件,而用低级函数(如read(),write())来存取共享文件(因为共享文件的内容变化频繁,很难对它进行缓冲)。
  11.有关宏不要将自增变量传递给宏:如:#define CUBE(x) ((x)*(x)*(x))……y=CUBE(++x);上述的y会被替换为:y=((++x)*(++x)*(++x));也就是x自增了3次。可以用enum关键字声明一串相关的常量,这样比#define要好:如:enum Error_code{OUT_OF_MEMORY;LOGIC_ERROR;FILE_NOT_FOUND;};因为:它使程序更容易维护,更易读,更容易调试。注意:一般来说,应该用宏去替换小的、可重复的代码段;当任务比较复杂,需要多行代码才能实现时,或者要求程序越小越好时,就应该使用函数。一些标准预定义宏:__LINE__,__FILE__,__DATE__,__TIME__,__STDC__(当要求程序严格遵守ANSI C标准时该标识符被赋值为1),__cplusplus(当编写C++程序时该标识被定义)调试中可以利用宏:#define DEBUG_VALUE(v) printf("I am in file:%s,line:%d,and"#v"is equal to %d.\n",__FILE__,__LINE__,v)….int x=20;DEBUG_VALUE(x);当然也可以打印时间日期等。#pragma的作用:用来激活或终止编译程序所支持的一些编译功能。如:#pragma loop_opt(on) //激活支持循环优化功能#pragma loop_opt(off) //取消循环优化功能#pragma warn -100 //取消具有特征码100的警告信息等#line的作用:用来重设__LINE__和__FILE__的基准值,如:#line 752, "XSOURCE.x" //以后引用__LINE__和__FILE__的时候都以这2个为基准如何取消已定义宏:使用#undef ##的作用:##的作用是把两个独立的字符串连接成一个字符串,如:#include #define SORT(data_type) sort_##data_typevoid sort_int(int **i);void sort_long(long **i);void sort_float(float **i);int main(){int **ip;long **lp;float **fp;…..sort(int) (ip);sort(long) (lp);sort(float) (fp);}
  12.有关内存释放一个指针后,最好把它设为NULL. NULL为空指针的意思,而NUL为'\0',虽然二者都可以简单的定义为0,但这不是一种可取的方式. malloc()函数不对分配的空间初始化,而calloc()函数会对分配的空间初始化为0.
  13.有关函数(1)只在当前源文件中使用的函数应该声明为内部函数(static),尽量不要把数组作为参数传递给函数,而应该使用指针.如: void byval_function(int []) ->>>> void byval_function(const int *).(2)用PASCAl修饰符声明的函数效率比普通c函数要高一些.但是现在也几乎没有明显的差别了.只有当几个毫秒的运行时间对你的程序也很重要时,你就应该用PASCAL修饰符来说明你的函数.(3)当你退出main()函数后,还可以执行其它一部分代码,如下:#include #include void close_files(void);void print_registration_message(void);int main(){….atexit(print_registration_message);atexit(close_files);…exit(0);}上例中,通过atexit()函数指示程序退出main()后自动调用close_files()和print_registration_message,注意调用的此项与atexit()的顺序相反,如上例中,将会先执行close_files,然后执行print_registration_message.还有一点就是,加入atexit()的函数要用void说明且不能带参数!! 
  14.有关位用宏来处理标志和位更方便,如: #define BIT_POS(N) (1U<<(N)) #define SET_FLAG(N,F) ((N)|=(F)) #define CLR_FLAG(N,F) ((N)&=~(F)) #define TST_FLAG(N,F) ((N)&(F)) #define BIT_RANGE(N,M) (BIT_POS((M)+1-(N))-1<<(N)) #define SET_MFLAG(N,F,V) (CLR_FLAG(N,F),SET_FLAG(N,V))  等,例子:BIT_POS(2)为100,BIT_RANGE(3,5)为111000注意同样不要对参数进行++,--操作!!