c语言总结| 耗时:0.186秒|59982条结果

C语言指针总结

C语言中的精华是什么,答曰指针,这也是C语言中唯一的难点。 C是对底层操作非常方便的语言,而底层操作中用到最多的就是指针,以后从事嵌入式开发的朋友们,指针将陪伴我们终身。 本文将从八个常见的方面来透视C语言中的指针,当然,还有其他没有具体提到的方面,像指针表达式、指针安全等问题,以后有机会我再慢
http://www.itnose.net/detail/482741.html 2014-01-23 18:00   [C语言]

c语言 指针总结

iPoint指向下一个变量。 5.*++iPoint 是先使iPoint指向下一个变量,然后再取出下一个变量的值。 5.指针的一个很重要的特点是可以改变实参指针变量所指变量的值,这时指针是当做函数的参数来传递的。 6.在c语言中数组名代表着首元素的地址。 7,指针变量与数组:iPoint+i等价于
http://www.itnose.net/detail/482638.html 2014-01-23 17:59   [C语言]

C语言总结

早期的C 语言主要是用于UNIX系统。 目前最流行的C语言有以下几种:     ·Microsoft C 或称 MS C     ·Borland Turbo C 或称 Turbo C     ·AT&T C 这些C语言版本不仅实现了ANSI C标准,而且在此基础上各自作了一些扩充,使之更加方便、完美。 C语言简洁、紧凑,使用方便、灵活。ANSI C一共只有32个关键字,9种控制语句, 注意:在C语言中,关键字都是小写的。 C语言规定,源程序中所有用到的变量都必须先说明,后使用,否则将会出错。这一点是编译型高级程序设计语言的一个特点,与解释型的BASIC语言是不同的。 1.1    C源程序的结构特点     1.一个C语言源程序可以由一个或多个源文件组成。     2.每个源文件可由一个或多个函数组成。     3.一个源程序不论由多少个文件组成,都有一个且只能有一个main函数,即主函数。     4.源程序中可以有预处理命令(include 命令仅为其中的一种),预处理命令通常应放在源文件或源程序的最前面。     5.每一个说明,每一个语句都必须以分号结尾。但预处理命令,函数头和花括号“}”之后不能加分号。     6.标识符,关键字之间必须至少加一个空格以示间隔。若已有明显的间隔符,也可不再加空格来间隔。 在使用标识符时还必须注意以下几点:     (1)标准C不限制标识符的长度,但它受各种版本的C 语言编译系统限制,同时也受到具体机器的限制。例如在某版本C 中规定标识符前八位有效,当两个标识符前八位相同时,则被认为是同一个标识符。     (2)在标识符中,大小写是有区别的。例如BOOK和book 是两个不同的标识符。     (3)标识符虽然可由程序员随意定义,但标识符是用于标识某个量的符号。因此,命名应尽量有相应的意义,以便于阅读理解,作到“顾名思义”。 在C语言中,数据类型可分为:基本数据类型,构造数据类型,指针类型,空类型四大类。 数值是以补码表示的: 正数的补码和原码相同; 负数的补码:将该数的绝对值的二进制形式按位取反再加1。 字符串常量和字符常量是不同的量。它们之间主要有以下区别: 1)        字符常量由单引号括起来,字符串常量由双引号括起来。 2)        字符常量只能是单个字符,字符串常量则可以含一个或多个字符。 3)        可以把一个字符常量赋予一个字符变量,但不能把一个字符串常量赋予一个字符变量。在C语言中没有相应的字符串变量。这是与BASIC 语言不同的。但是可以用一个字符数组来存放一个字符串常量。在数组一章内予以介绍。 字符常量占一个字节的内存空间。字符串常量占的内存字节数等于字符串中字节数加1。增加的一个字节中存放字符"\0" (ASCII码为0)。这是字符串结束的标志。 变量的数据类型是可以转换的。转换的方法有两种,一种是自动转换,一种是强制转换。自动转换发生在不同数据类型的量混合运算时,由编译系统自动完成。自动转换遵循以下规则: 1)        若参与运算量的类型不同,则先转换成同一类型,然后进行运算。 2)        转换按数据长度增加的方向进行,以保证精度不降低。如int型和long型运算时,先把int量转成long型后再进行运算。 3)        所有的浮点运算都是以双精度进行的,即使仅含float
http://www.itnose.net/detail/478203.html 2014-01-22 18:47   [C语言]

C语言之精华总结

][0]或者*(*a)),也即第一个整数(第一行第一列的第一个整数)。如果采用这种表达式,就非常的笨拙,所以a[0][0]记法上的简便就非常的有用了!简单明了! 对于数组,你只能取用在数组有效范围内的元素和元素地址,不过最后一个元素的下一个元素的地址是个例外。它可以被用来方便数组的各种计算,特别是比较运算。但显然,它所指向的内容是不能拿来使用和改变的! 关于数组本身大概就这么多,下面简要说一下数组和指针的关系。它们的关系非常暧昧,有时候可以交替使用。 比如 int main(int args, char* argv[])中,其实参数列表中的char* argv[]就是char** argv的另一种写法。因为在C语言中,一个数组是不能作为函数引数(argument)【注8】直接传递的。因为那样非常的损失效率,而这点违背了C语言设计时的基本理念??作为一门高效的系统设计语言。 注8:这里我没有使用函数实参这个大陆术语,而是运用了台湾术语,它们都是argument这个英文术语的翻译,但在很多地方中文的实参用的并不恰当,非常的勉强,而引数表示被引用的数,很形象,也很好理解。很快你就可以像我一样适应引数而不是实参。 dereferance,也就是*运算符操作。我也用的是提领,而不是解引用。 我认为你一定智勇双全:既有宽容的智慧,也有面对新事物的勇气!你不愿意承认吗?:) 所以在函数参数列表(parameter list)中的数组形式的参数声明,只是为了方便程序员的阅读!比如上面的char* argv[]就可以很容易的想到是对一个char*字符串数组进行操作,其实质是传递的char*字符串数组的首元素的地址(指针)。其它的元素当然可以由这个指针的加法间接提领(dereferance)【参考注8】得到!从而也就间接得到了整个数组。 但是数组和指针还是有区别的,比如在一个文件中有下面的定义: char myname[] = “wuaihua”; 而在另一个文件中有下列声明: extern char* myname; 它们互相是并不认识的,尽管你的本义是这样希望的。 它们对内存空间的使用方式不同【注9】。 对于char myname[] = “wuaihua”如下 myname w u a i h u a \0 对于char* myname;如下表 myname \|/ w u a i h u a \0 注9:可以参考Andrew Konig的《C陷阱与缺陷》4.5节。 改变的方法就是使它们一致就可以了。 char myname[] = “wuaihua”; extern char myname[]; 或者 char* myname = “wuaihua”;//C++中最好换成const char* myname = “wuaihua”。 extern char* myname; C之诡谲(下) 三.类型的识别。 基本类型的识别非常简单: int a;//a的类型是a char* p;//p的类型是char* …… 那么请你看看下面几个: int* (*a[5])(int, char*); //#1 void (*b[10]) (void (*)()); //#2 doube(*)() (*pa)[9]; //#3 如果你是第一次看到这种类型声明的时候,我想肯定跟我的感觉一样,就如晴天霹雳,五雷轰顶,头昏目眩,一头张牙舞爪的狰狞怪兽扑面而来。 不要紧(Take it easy)!我们慢慢来收拾这几个面目可憎的纸老虎! 1.C语言中函数声明和数组声明。 函数声明一般是这样int fun(int,double);对应函数指针(pointer to function)的声明是这样: int (*pf)(int,double),你必须习惯。可以这样使用: pf = &fun;//赋值(assignment)操作 (*pf)(5, 8.9);//函数调用操作 也请注意,C语言本身提供了一种简写方式如下: pf = fun;// 赋值(assignment)操作 pf(5, 8.9);// 函数调用操作 不过我本人不是很喜欢这种简写,它对初学者带来了比较多的迷惑。 数组声明一般是这样int a[5];对于数组指针(pointer to array)的声明是这样: int (*pa)[5]; 你也必须习惯。可以这样使用: pa = &a;// 赋值(assignment)操作 int i = (*pa)[2]//将a[2]赋值给i; 2.有了上面的基础,我们就可以对付开头的三只纸老虎了!:) 这个时候你需要复习一下各种运算符的优先顺序和结合顺序了,顺便找本书看看就够了。 #1:int* (*a[5])(int, char*); 首先看到标识符名a,“[]”优先级大于“*”,a与“[5]”先结合。所以a是一个数组,这个数组有5个元素,每一个元素都是一个指针,指针指向 “(int, char*)”,对,指向一个函数,函数参数是“int, char*”,返回值是“int*”。完毕,我们干掉了第一个纸老虎。:) #2:void (*b[10]) (void (*)()); b是一个数组,这个数组有10个元素,每一个元素都是一个指针,指针指向一个函数,函数参数是“void (*)()”【注10】,返回值是“void”。完毕! 注10:这个参数又是一个指针,指向一个函数,函数参数为空,返回值是“void”。 #3. doube(*)() (*pa)[9]; pa是一个指针,指针指向一个数组,这个数组有9个元素,每一个元素都是“doube(*)()”【也即一个指针,指向一个函数,函数参数为空,返回值是“double”】。 现在是不是觉得要认识它们是易如反掌,工欲善其事,必先利其器!我们对这种表达方式熟悉之后,就可以用“typedef”来简化这种类型声明。 #1:int* (*a[5])(int, char*); typedef int* (*PF)(int, char*);//PF是一个类型别名【注11】。 PF a[5];//跟int* (*a[5])(int, char*);的效果一样! 注 11:很多初学者只知道typedef char* pchar;但是对于typedef的其它用法不太了解。Stephen Blaha对typedef用法做过一个总结:“建立一个类型别名的方法很简单,在传统的变量声明表达式里用类型名替代变量名,然后把关键字 typedef加在该语句的开头”。可以参看《程序员》杂志2001.3期《C++高手技巧20招》。 #2:void (*b[10]) (void (*)()); typedef void (*pfv)(); typedef void (*pf_taking_pfv)(pfv); pf_taking_pfv b[10]; //跟void (*b[10]) (void (*)());的效果一样! #3. doube(*)() (*pa)[9]; typedef double(*PF)(); typedef PF (*PA)[9]; PA pa; //跟doube(*)() (*pa)[9];的效果一样! 3.const和volatile在类型声明中的位置 在这里我只说const,volatile是一样的【注12】! 注12:顾名思义,volatile修饰的量就是很容易变化,不稳定的量,它可能被其它线程,操作系统,硬件等等在未知的时间改变,所以它被存储在内存中,每次取用它的时候都只能在内存中去读取,它不能被编译器优化放在内部寄存器中。 类型声明中const用来修饰一个常量,我们一般这样使用:const在前面 const int;//int是const const char*;//char是const char* const;//*(指针)是const const char* const;//char和*都是const 对初学者,const char*;和 char* const;是容易混淆的。这需要时间的历练让你习惯它。 上面的声明有一个对等的写法:const在后面 int const;//int是const char const*;//char是const char* const;//*(指针)是const char const* const;//char和*都是const 第一次你可能不会习惯,但新事物如果是好的,我们为什么要拒绝它呢?:)const在后面有两个好处: A. const所修饰的类型是正好在它前面的那一个。如果这个好处还不能让你动心的话,那请看下一个! B.我们很多时候会用到typedef的类型别名定义。比如typedef char* pchar,如果用const来修饰的话,当const在前面的时候,就是const pchar,你会以为它就是const char* ,但是你错了,它的真实含义是char* const。是不是让你大吃一惊!但如果你采用const在后面的写法,意义就怎么也不会变,不信你试试! 不过,在真实项目中的命名一致性更重要。你应该在两种情况下都能适应,并能自如的转换,公司习惯,商业利润不论在什么时候都应该优先考虑!不过在开始一个新项目的时候,你可以考虑优先使用const在后面的习惯用法。 四.参数可变的函数 C语言中有一种很奇怪的参数“…”,它主要用在引数(argument)个数不定的函数中,最常见的就是printf函数。 printf(“Enjoy yourself everyday!\n”); printf(“The value is %d!\n”, value); …… 你想过它是怎么实现的吗? 1. printf为什么叫printf? 不管是看什么,我总是一个喜欢刨根问底的人,对事物的源有一种特殊的癖好,一段典故,一个成语,一句行话,我最喜欢的就是找到它的来历,和当时的意境,一个外文翻译过来的术语,最低要求我会尽力去找到它原本的外文术语。特别是一个字的命名来历,我一向是非常在意的,中国有句古话:“名不正,则言不顺。 ”printf中的f就是format的意思,即按格式打印【注13】。 注13:其实还有很多函数,很多变量,很多命名在各种语言中都是非常讲究的,你如果细心观察追溯,一定有很多乐趣和满足,比如哈希表为什么叫hashtable而不叫hashlist?在C++的SGI STL实现中有一个专门用于递增的函数iota(不是itoa),为什么叫这个奇怪的名字,你想过吗? 看文章我不喜欢意犹未尽,己所不欲,勿施于人,所以我把这两个答案告诉你: (1)table与list做为表讲的区别: table: -------|--------------------|------- item1 | kadkglasgaldfgl | jkdsfh -------|--------------------|------- item2 | kjdszhahlka | xcvz -------|--------------------|------- list: **** *** ******* ***** That's the difference! 如果你还是不明白,可以去看一下hash是如何实现的! (2)The name iota is taken from the programming language APL. 而APL语言主要是做数学计算的,在数学中有很多公式会借用希腊字母, 希腊字母表中有这样一个字母,大写为Ι,小写为ι, 它的英文拼写正好是iota,这个字母在θ(theta)和κ(kappa)之间! 下面有一段是这样的: APL is renowned for using a set of non-ASCII symbols that are an extension of traditional arithmetic and algebraic notation. These cryptic symbols, some have joked, make it possible to construct an entire air traffic control system in two lines of code. Because of its condensed nature and non-standard characters, APL has sometimes been termed a "write-only language", and reading an APL program can feel like decoding an alien tongue. Because of the unusual character-set, many programmers used special APL keyboards in the production of APL code. Nowadays there are various ways to write APL code using only ASCII characters. 在C++中有函数重载(overload)可以用来区别不同函数参数的调用,但它还是不能表示任意数量的函数参数。 在标准C语言中定义了一个头文件专门用来对付可变参数列表,它包含了一组宏,和一个va_list的typedef声明。一个典型实现如下【注14】: typedef char* va_list; #define va_start(list) list = (char*)&va_alist #define va_end(list) #define va_arg(list, mode) ((mode*) (list += sizeof(mode)))[-1] 注14:你可以查看C99标准7.15节获得详细而权威的说明。也可以参考Andrew Konig的《C陷阱与缺陷》的附录A。 ANSI C还提供了vprintf函数,它和对应的printf函数行为方式上完全相同,只不过用va_list替换了格式字符串后的参数序列。至于它是如何实现的,你在认真读完《The C Programming Language》后,我相信你一定可以do it yourself! 使用这些工具,我们就可以实现自己的可变参数函数,比如实现一个系统化的错误处理函数error。它和printf函数的使用差不多。只不过将stream重新定向到stderr。在这里我借鉴了《C陷阱与缺陷》的附录A的例子。 实现如下: #include #include void error(char* format, …) { va_list ap; va_start(ap, format); fprintf(stderr, “error: “); vfprintf(stderr, format, ap); va_end(ap); fprintf(stderr, “\n”); exit(1); } 你还可以自己实现printf: #include int printf(char* format, …) { va_list ap; va_start(ap, format); int n = vprintf(format, ap); va_end(ap); return n; } 我还专门找到了VC7.1的头文件看了一下,发现各个宏的具体实现还是有区别的,跟很多预处理(preprocessor)相关。其中va_list就不一定是char*的别名。 typedef struct { char *a0; /* pointer to first homed integer argument */ int offset; /* byte offset of next parameter */ } va_list; 其它的定义类似。 经常在Windows进行系统编程的人一定知道函数调用有好几种不同的形式,比如__stdcall,__pascal,__cdecl。在Windows下_stdcall,__pascal是一样的,所以我只说一下__stdcall和__cdecl的区别。 (1)__stdcall表示被调用端自身负责函数引数的压栈和出栈。函数参数个数一定的函数都是这种调用形式。 例如:int fun(char c, double d),我们在main函数中使用它,这个函数就只管本身函数体的运行,参数怎么来的,怎么去的,它一概不管。自然有main负责。不过,不同的编译器的实现可能将参数从右向左压栈,也可能从左向右压栈,这个顺序我们是不能加于利用的【注15】。 注15:你可以
http://www.itnose.net/detail/501461.html 2014-01-25 14:25   [C语言]

C语言编程细节总结

1、为一个数组初始化 int dest[64]={-1}; char * sfnames[8]={"s1.dat","s2.dat","s3.dat","s4.dat","s5.dat","s6.dat","s7.dat","s8.dat"};   2、char * 定义的字符串处了定义时给他赋值之外,之后的程序中不能对该变量进行改动。想要定义可以修改的字符串必须用char[]定义。   3、除了在字符串变量定义的时候可以用“=”给它赋值外,其他地方只能用strcpy 例如: char str[10]="GiantKing";  //定义式赋值 strcpy(str,"Better man");    4、定义两个字符串数组时: char * arr1[10],arr2[10];            //错误,arr1正确,但是arr2为字符数组 char * arr1[10],* arr2[10];         //正确   5、在VC6.0编译器下,int类型和long类型同占用4个byte,他们所能表示的范围是相同的。 C语言各类型取值范围:(VC6.0 环境下) char:                  -127                              127 unsigned char:      0                                   255 short:                  -32767                          32768 unsigned short:    0                                   65535 int:                      -2147483648[-2^31]      2147483647[2^31-1] unsigned int:         0      
http://www.itnose.net/detail/494000.html 2014-01-24 15:49   [C语言]

C语言位运算总结

C语言的位运算在检测与控制领域很常用,位运算包括 &     按位于   主要用于清零、取一个数中的某些指定位、保留某些位 |      按位或    ^     异或  
http://www.itnose.net/detail/493855.html 2014-01-24 15:28   [C语言]

C语言学习总结

负责的RCP这部分在功能性上基本上也算是告一段落,目前主要就是带带新人,在框架型上进一些指导工作,而学习C语言对我来说有两大好处:1.考试是需要考C和数据结构的;2.学习C语言能够帮助我理解程序在执行过程中的运行情况,能更清楚的了解计算机底层结构。     
http://www.itnose.net/detail/482605.html 2014-01-23 17:59   [C语言]

C语言基础复习总结

大一学的C++,不过后来一直没用,大多还给老师了,最近看传智李明杰老师的ios课程的C语言入门部分,用了一周,每晚上看大概两小时左右,效果真是顶一学期的课,也许是因为有开发经验吧,废话少说,直接把总结贴出来了~   #include <stdio.h> int main
http://www.itnose.net/detail/478153.html 2014-01-22 18:47   [基础]

C 语言预处理命令总结大全

C程序的源代码中可包括各种编译指令,这些指令称为预处理命令。虽然它们实际上不是C语言的一部分,但却扩展了C程序设计的环境。本节将介绍如何应用预处理程序和注释简化程序开发过程,并提高程序的可读性。ANSI标准定义的C语言预处理程序包括下列命令: #define,#error,#include,#if
http://www.itnose.net/detail/6687353.html 2016-12-14 05:36   [C语言]

C语言常见陷阱总结

1.  1 char array1[100]; 2 char* array2; 二者都可以当指针用,使用时有何区别?   1中字符数组,存储于栈区,sizeof(array1)为100,aray1代表首元素首地址,不可以被赋值,array1[0]这样可以被赋值,数组大小确定,难以扩容   2中存储于字符常量区,sizeof(array2)为4,array2可以被赋值,array2[0]不可以被赋值,空间大小可变 2. 32位中,long型和指针占4个字节,64位中占8个字节 3. strcat连续拼接字符串时,每次都要找到结尾,解决方案时给结尾做标记 4. const在C语言中为只读变量,C++中为常量 5. 一般机器默认小端存储,苹果机器为大端存储 6. char a[] = "Hello world";   sizeof(a) = 12    注:sizeof时算 \0 ,strlen时不算 \0   sizeof(*a) = 1      sizeof(&a) = 4  当字符数组以函数参数形式存在时,sizeof为4 7. printf函数执行时,计算顺序是从后向前,输出顺序是从前向后,可以将printf函数中想成传入的参数,存储于栈区 8.  int a[] ={2,8,16,24}; char* p1 = a; char* p2 = &a[3]; p2-p1 = ? 本题中,两个地址相减,求的是指针的偏移量,p1指向的内容是2,p2指向的内容是24,p2-p1为3个4字节大小,为12,又因为p1和p2的基本类型为1个字节,所以(3*4)/1=12 9. 括号表示表达式,表达式要变成int类型计算;有符号要向无符号类型转换;小类型要向大类型转换 10. (a=1)?2:3 a=1,表示整个表达式的值为1,所以输出为2 11.switch case语句中,每个case过后如若没有遇到break,则顺次向下执行 12.char a[100] = {'a','b','c','\0','1','2'};  遇到  '\0'  字符串结束    char a[100] = {'a','b','c','0','1','2'};   '0' 代表字符不是'\0'      char a[100] = {'a','b','c',0,'1','2'};     '\0'就是0,字符串结束 13.阿克曼函数  ack(1,k) = 2+x;   ack(2,x) = 3+2*x;   ack(3,x) = 2^(x+3)-3; 14.在移位运算时,数据需要补位时,符号位是什么就补什么      如 int i = 0xf0000000;i >>=1; i=0xf8000000 15.常量数值不能进行自加自减运算 16.注意与或运算,与运算时,前面表达式为假时,与运算符后面的表达式不参与运算,输出为假,若为真,则再计算与运算符后面的表达式      或运算时,或运算符前面的表达式为真时,不计算或运算符后面的表达式,直接输出为1,若为假,则在计算或运算符后面的表达式 17.sizeof返回值为unsigned int,当int遇到了unsigned int 时,转换为unsigned int进行计算 18.栈区空间的生命周期在遇到}时截止,在}之前返回栈区申请的空间不存在,因为被回收了,字符常量区的内容只能读不能向里面写  
http://www.itnose.net/detail/6531764.html 2016-07-14 12:50   [C++]

C语言之起泡排序法总结

代码学习过程中总结一下起泡排序法。 起泡排序法的基本思路:每次将相邻的两个数进行比较,将小的调到前头。 若有6个数:9,8,5,4,2,0 第一次先将最前面的两个数8和9对调,第二次将第二个和第三个数(9和5对调)……如此一共进行五次,得到8,5,4,2,0,9的顺序。 可以看到,大数已沉底
http://www.itnose.net/detail/6520517.html 2016-06-16 19:52   [C语言]

#C语言#起泡排序法的总结

新手小白,代码学习过程中总结一下起泡排序法。 起泡排序法的基本思路:每次将相邻的两个数进行比较,将小的调到前头。若有6个数:9,8,5,4,2,0第一次先将最前面的两个数8和9对调,第二次将第二个和第三个数(9和5对调)……如此一共进行五次,得到8,5,4,2,0,9的顺序。 可以看到,大数已沉底
http://www.itnose.net/detail/6520412.html 2016-06-16 15:47   [C语言]

C语言中进制知识总结

c后面1个数,由于c+1满16,需要进1位,个位数变为0,所以16的十六进制是10。 最终十进制数18的十六进制转换结果是12 详细结果如下图所示(C语言把数字前面加0x的数认为是十六进制数) 3.C语言中int类型进制的声明以及占位符 虽然以下3个变量的赋值方式不同,但实际赋值结果都是18
http://www.itnose.net/detail/6499996.html 2016-05-08 02:53   [C语言]

个人c语言编程风格总结

  总结一下我个人的编程风格及这样做的原因吧,其实是为了给实验室写一个统一的C语言编程规范才写的。首先声明,我下面提到的编程规范,是自己给自己定的,不是c语言里面规定的。   一件事情,做成和做好中间可能隔了十万八千里。   同样的,代码的质量也极大程度上反映了编程者的水平高低。为了让大家从学习的开始就养成良好的编程习惯,创作出优质的代码,实验室编辑这个文档,作为大家编程的参考,同时也是对以后编程风格的硬性规定。   对于一个团队来讲,制定统一的编程规范,好处是显而易见的。通常一个项目是由多个成员共同完成,在项目中,经常互相调用组内成员的代码。如果两个人的编程习惯和风格差异显著,那么将会浪费大量时间在读懂代码上。相反,一致而良好的编程规范,会让合作开发变得轻松而高效。   众所周知,C语言是面向过程的语言。也就是说,程序员要对程序的每一步有精准的把握,知道每一条程序语句的执行内容及其结果。因而,代码的可读性就显得尤为重要。这里的可读,不仅仅是对自己可读,也要对其他人可读。一段只有自己能读懂的代码,可以说价值很低,而且这样的代码随着时间的推移往往自己也读不懂。而可读性强的代码,不仅方便移植与交流,更给调试带来了难以估量的便利。   读一段好的代码,会有一种读英语文章的流畅感。尽管C语言提供了有限的32个关键字,但是变量、函数等的命名却提供了较大的自由,这也是我们将代码语句化的基础。试想,如果一段代码有了主谓宾结构,即使不懂编程的人,也能明白代码的功能。而这正是我们代码编辑者追求的目标。   所以,写好一段代码,从把你的代码读者当编程小白开始!   一、文件管理     每一个做技术的人,无论软硬件,计算机里都应该有一个纯英文的盘符,注意我是说英文,而不是pinyin。在这个纯英文的盘符里,当然是存放各种技术相关的软件、程序以及文档。而这些内容的命名也应该是英文的,包括各个子文件夹。其他诸如即时通讯软件、游戏文件等应该放在其他盘符内。一方面,这样是对自己英文水平的锻炼;另一方面,也能避免很多在使用国外软件的时候出现的各种BUG。   每一个软件,都应该放在一个独立的文件夹中。这样既方便查找,又避免混乱。因为我们都知道每一个软件完成后,都不仅仅是一个exe文件那么简单,通常还有各种后缀的文件,而这些我们都不能删除。如果打开D盘时,映入眼帘的是几万个由不同软件安装时生成的各种文件,相信给谁都会一脸大写的懵逼。因此,将不同的软件放在单独的文件夹下是非常有必要的。   不同IDE下编写的程序,也都应该存放在独立的一个文件夹下。文件夹内,不同的工程也应该分别建立文件夹,并合理而精准命名。这样为日后的查找带来极大的便利。   很多IDE在编写程序文件时,除了要建立Project(工程),还要建立Workspace,即工作空间。工作空间通常是指定一个空间(也就是文件夹),IDE启动时,自动打开该空间下的各个Project。因此,一个Workspace可以存放多个Project。这样我们就可以利用Workspace管理自己在该IDE下编写的各个Project。前提是你建立了Workspace,而Project存放在这个Workspace下。   每一个独立的项目都应该是一个独立的Project。例如,分别练习编写流水灯和数码管的程序时,要分别建立Project,而不能放在一个Project下,除非你的项目同时用到了流水灯和数码管。这样做的好处是你可以Project名称上精确获得其内容信息,而不会出现程序写完过一段时间后无从查找的情况。 二、命名规则     首先说一下总的命名规则:命名一定要用英文。并不是因为拼音不可以,而是因为我们要与国际接轨,要养成良好的英文书写习惯。其次,命名中除了“\/:*?”<>|“等系统不允许的字符外,也不能出现除英文字母、下划线、数字外的其他字符。如果你想命名成flash LED.c,中间的空格要用下划线”_“来代替,写成flash_LED.c。另外,命名中可以出现必要的数字。 1、文件/文件夹命名   文件命名要精确,文件名要准确反映文件内容。写的是   文件命名一律使用小写字母,如keyboard.c。   如有缩写单词,则必须大写,如flash_LED.c、UART.c。其中LED是Light-Emitting Diode(发光二极管)的缩写,UART是Universal Asynchronous Receiver/Transmitter(通用异步收发器,也就是串口)的缩写。对于有约定俗成缩写的单词,就使用缩写词汇。   文件名应使用名词,而不应该使用动词。如果文件内容是数据采集,应该命名为data_collection.c而非data_collect.c。 2、标识符命名      C语言中,可以定义各种标识符作为变量名、数组名、函数名、标号及用户定义对象的名称。ANSI C规定标识符必须由字母和下划线开始,随后可以出现字母、下划线和数字。 1)变量命名      变量命名一律小写,缩写词汇用大写,且全部使用名词,可以使用形容词修饰,用“_”表从属关系。因为变量名作为一个变量的名字,就应该是一个名词。      局部循环体控制变量用i,j,k。如for(i=0;i<100;i++)。      指针变量用“p_”开头,后面接指向内容。如指向高度变量的指针,命名为“*p_height”。请读者自行区分指针和指针变量的区别。      局部变量尽量用一个单词表达清其含义。      全局变量命名时首先写所属模块名称。例如如一个传感器文件sensor.c里面的一个全局变量要代表温度,则命名为sensor_temperature。又例如LCD(液晶显示屏)文件LCD.c中表示LCD状态的全局变量命名为LCD_status。因为全局变量往往跨文件调用,如不写清变量定义位置,当程序庞大,而IDE又不支持一键定位时,查找起来很麻烦。即使IDE支持一键定位,一个清楚明白的命名,能让人瞬间读懂该变量的含义。 2)数组命名      数组命名各单词首字母大写,其他同变量。 读者可能会有疑问,数组名后面会有[]符号,与变量区别明显,为什么要用首字母大写的方式。实际上,在数组名作为实参传递数组首地址时,往往会省略[]符号,应该数组名就是数组的首地址。例如:   unsigned char string[]=”abcdefg”;    printf(“%s”,string);   在以上代码中,string是一个8位数组(为什么是8位?),在使用printf()函数输出时,只写了数组名,显然这种方式是被允许的。而此时就没有写[],在这种情况下,并不能瞬间知道string是变量还是数组,而需要参考前面的格式控制符“%s”。在其他函数中,或许没有“%s”这样的格式控制符帮助我们判断string到底是数组还是变量,我们只有找到函数的声明或定义才能知道答案,严重影响阅读。因此有必要对数组和变量加以区分。 3)函数命名   函数命名各单词首字母大写,写成主谓语形式,主语用名词,谓语用动词,缩写词汇用大写,用“_”表从属关系。主语通常为模块名,而谓语是描述模块的动作。因为函数本身就是用来执行一系列的动作的, 结合函数参数,可以表达通顺的语句。举个简单的例子:延时函数。定义一个ms级延时函数为:   void Delay(unsigned int ms);(这个其实是声明,函数体不想写了)   调用时写:   Delay(500);   很显然是延时了500ms。而如果再用个宏定义:   #define MS500 500   Delay(MS500);   是不是更一目了然呢?   另外还比如串口发送函数命名UART_TX( ),调用时写成:   UART_TX(time); (通常发送数据Transmit Data简写为TXD)   显然意思是串口发送时间数据。   再比如设置参考值的函数命名为REF_Set( ),调用时写成:   REF_Set(current_voltage);(通常参考值Reference简写为REF)   显然意思是将当前的电压设置为参考值。   主谓格式的命名大大增加了代码的可能性。   当然,函数命名中必要时可以出现宾语。这种情况多出现在函数没有参数的情况下。如一个函数的功能是LCD显示时间,而时间是全局变量,因此这个函数就不需要参数,此时直接定义成void LCD_Display_Time(void)(其实是声明,因为没写函数体)。   命名时首字母大写不会和数组混淆吗?显然不会,因为函数不论是在定义、声明还是调用的时候后面都必须跟着”( )”。  4)标号命名   由于在硬件编程中标号可以用循环来代替,所以很少用到。我们规定标号的命名格式基本同变量,使用全部小写的名词,但是只用一个单词表示即可。因为标号时候的时候或者前面加了goto,或者后面加了“:”,很容易与变量区分开。况且只是一个定位标志,所以一个单词足够了。 5)自定义类型命名   自定义类型命名主要指使用typedef定义的新类型名,以及结构体类型、共用体类型的类型名(而非该类型的变量名)。   自定义的新类型名,只用一个单词,首字母大写。但是定义这种新类型的变量时,命名规则与变量命名规则完全相同。   请自行体会新类型名与新类型变量的区别。 6)宏定义命名   宏定义命名全部使用大写字母,单词数不限。可以加入数字和下划线,但是数字不能开头 。   由于宏定义的特殊性,对其使用名词或动词不作规定。因为宏定义一个函数时,应该是动词性质,而宏定义一个常数时,应该是名词性质。   三、表达式书写     表达式书写时,最重要的是意义明确。由于C语言不同运算符有着不同的结合顺序和优先级,因此很容易造成歧义,即实际运算顺序与设想运算顺序不同。除了完全理解并熟记结合顺序与优先级,最简单的方法就是用括号来明确运算顺序——在表达式中,括号的优先级是最高的。   另外,运算符与其操作数之间要空格。如:   a=a+b;   应写成:   a  =  a  +  b;   这样做可以让表达式显得不那么拥挤而增加可读性,但这不是重点。这样做的重点是帮我们避免很多不易识别的错误。如:   a=a/*b;   我们的本意是a除以指针变量b指向的内容,然后将商赋给a。然而残酷的现实是,编译器发现了连起来的“/*”,没错,这是注释符。所以,后面的内容都会被注释掉,直到找到最近的“*/”。   所以我们应该写成:   a  =  a  /  *b;       //指针运算符*应该紧跟指针变量b   或者:   a=a/(*b);        //不过即便这样写也应该加入空格,便于阅读   有人会说,现在的IDE会用不同的颜色提示注释内容,所以这样的错误应该不会出现。但是我想说的是,作为一个立志做合格的工程师的你,会允许自己有不严谨的习惯吗?况且本身我们的文档是为了在C语言语法、词法基础上,制定一个编程规范。   另外,有些老版本的C编译器允许用=+来代替+=的含义,即复合赋值号的两个符号顺序可以是反的。这样的话,如果写出:   a=-1;   本意是将-1赋给a,但是编译器却会理解成:   a  =  a  -  1;   显然意义完全变了。   有人又会说了,你不是说老版本的C编译器嘛,我不用不就行了吗。然而,我们要考虑代码的可移植性,就绝不应该允许这样的想法。   因此,在书写表达式的时候,不要吝惜你的空格和括号。   还有一点值得说明的是,复合赋值运算符的两个运算符不能分开。如“+=”不能写成“+  =”。   四、文件编写   1、文件划分   一个简单的程序,只有几行到几十行,放在一个文件内一目了然。但是一个较大的项目中可能会有成千上万行代码,更有大型程序代码数以百万行计。这样规模的代码,存放在一个文件内,其恐怖程度请自行想象。   当一个函数的代码量超过几十行时,就应该考虑有没有可能把其中某些代码提取出来打包成另一个函数然后调用。同样的,当一个文件的代码量超过几百行时,就应该考虑有没有可能把一些函数分出来放到别的文件中去。这样做都是为了程序的可读性和方便调试,毕竟一个较短的函数功能测试要比一个长函数容易得多。   然而,一个更好的划分文件的依据应该是按模块划分。当然,相应的划分函数的依据应该是按功能划分。也就是说,一个文件存放一个模块的内容,一个函数完成单一的功能。 2、文件内容   在C语言编程时,有两种文件。一种是源文件(source file,后缀为.c),另一种是头文件(head file,后缀为.h)。   C语言的编译是以c文件为单位的,因此只有h文件时是无法编译的。根据项目规模大小,一个项目可以由单个c文件构成,也可以有多个c文件和h文件共同构成。   C语言编译器在编译时,通常经历以下步骤:   预处理→语法、词法分析→编译→汇编→链接。   预处理阶段,将根据预处理指令来修改c文件内容。其中,预处理指令包括宏定义(#define)、条件编译指令(#ifdef、#ifndef、#endif等)、头文件包含指令(#include)、特殊符号(LINE、FILE等)。对于头文件包含指令来讲,其作用是将所包含h文件中的内容替换到包含指令处,当然如果内容中有其他预处理指令,也会做相应处理。   因此,h文件在编译时将插入到c文件中。由此可见,h文件可以出现任何符合c语言语法的内容,但是在实际编程中,我们显然不会这样做,因为这样做就失去了区分c文件和h文件的意义。   h文件最大的意义是作为对外接口使用,在发布库文件时作用更是明显。也就是说,h文件的内容用来提供供其他文件或函数调用的函数原型、变量等内容。下面具体来规定c文件和h文件中应该出现的内容: 源文件(.c) 头文件(.h) 头文件包含指令(#include) 头文件包含指令(#include)   宏定义(#define) 所有函数定义(必须有函数体,即{ }) 内部函数声明(static,不能有函数体) 外部函数声明(extern,不能有函数体) 外部变量定义(必须赋初值) 静态外部变量定义(static,必须赋初值) 外部变量声明(extern,不能赋值)     自定义类型(typedef) 外部数组定义 静态内部数组定义(static) 外部数组声明(const) 条件编译 条件编译     由上表可以看出,h文件内存放的都是对外可见的变量、函数数组等的声明,宏定义则是对内对外都可以使用,放在这里主要为了修改方便。在定义外部变量、数组和函数时,不需要写extern,因为缺省时默认extern。而声明外部变量、数组和函数时,必须用extern显式声明,这样是为了让代码更直观。   函数说明是必须要写的,写清函数的入口、出口参数及其功能,以及其它说明,对于代码维护和改写能带来极大的方便。   通常,如果h文件中全部是对外接口,而对应c文件中各函数均不调用本文件中的其他内容(变量、函数等),也可以不用包含自身的h文件。   另外,程序编写时,缩进要规范,要能表达所属层次关系。每次缩进4个字符,不能随意缩进。   关于函数体或组合语句使用{}的格式,常见的有两种格式:   int main( ){   }   或者:   int main( )   {   }   本人比较偏向第一种,因为可以节省行数,让程序紧凑。但是这个问题见仁见智,有人觉得第一种不如第二种对齐方式层次分明。所以这个就让两种方式并存吧。因为其他问题不涉及审美习惯,只要规定好大家执行就好了,这个毕竟涉及到每个人的审美不同。   h文件中必须在开头和末尾写条件编译:   #ifndef __全大写文件名_H__   (或者写成:全大写文件名_H__)   #ifndef __全大写文件名_H__   …(文件内容)   #enif   这样做是为了防止多次包含,保证在编译时前面已经替换过该头文件,后面将不再替换,否则有些内容可能重复定义。   下面用代码示例: <protocol.h>:(每一个h文件中必须有√标注的内容)   #include <msp430x14x.h>        //头文件包含   #ifndef __PROTOCOL_H__     //条件编译      √   #define __PROTOCOL_H__     //条件编译      √   //#define MONITOR_TERMINAL     //条件编译    #define MONITOR_NODE1          //条件编译    //#define MONITOR_NODE2        //条件编译    #define MATCHING_CODE  0x55    //宏定义   #define HOST_ADDRESS   0x40    //宏定义   #define NODE_1 0x41            //宏定义   #define NODE_2 0x42            //宏定义   typedef struct {               //自定义类型           float start_bit;         float TXD_data;         float stop_bit;   }TX_Data;   extern unsigned char Tx_Data_Packet[];   //外部数组声明   extern unsigned char Rx_Data_Packet[];   //外部数组声明   extern unsigned char protocol_set_flag;  //外部变量声明   extern unsigned char Extract_Data(void); //外部函数声明   #endif                          //条件编译    √ <protocol.c>:   #include<math.h>             //头文件包含,系统库函数用<>   #include"protocol.h"        //头文件包含,系统库函数用“”   Static unsigned char easy_delay(void); //内部函数声明   unsigned char protocol_set_flag = 0; //外部变量定义   unsigned char Tx_Data_Packet[6] = {'0','1','2','3','4','5'};   //外部数组定义        unsignedchar Rx_Data_Packet[6] = {'0','1','2','3','4','5'};  //外部数组定义        Static char temp_Packet[6] = {'0','1','2','3','4','5'};             //静态外部数组定义,只能本文件使用 /******************************************************** *名        称:Extract_Data() *功        能:提取接收到的数据帧 *入口参数:无 *出口参数:1-成功,0-失败 *说        明: ********************************************************/ unsigned char Extract_Data(void){   unsigned char temp = 0;   temp = Rx_FIFO_ReadChar();   if( temp == MATCHING_CODE ){         UART_TX_OPEN();         Rx_Data_Packet[0] = temp;         Rx_Data_Packet[1] = Rx_FIFO_ReadChar(); //来源         Rx_Data_Packet[2] = Rx_FIFO_ReadChar(); //去向         Rx_Data_Packet[3] = Rx_FIFO_ReadChar(); //光照+温度高         Rx_Data_Packet[4] = Rx_FIFO_ReadChar(); //温度低         Rx_Data_Packet[5] = '\0';       return (1);   }                           else return (0); }                     //外部函数定义,必须在前面写函数说明 /******************************************************** *名        称: easy_delay() *功        能:简单延时 *入口参数:无 *出口参数:无 *说        明: ********************************************************/ Static unsigned char easy_delay(void){   unsigned int i = 0;   for( i=0; i<1000 ; i++); }      //内部函数定义,必须在前面写函数说明,且在本文件前部声明以便阅读   这两个文件都是从编者曾经写的代码中截取出来的,有些部分是为了演示内容现在添加进去的,源代码中不存在,请大家不必在意细节,关键领会两个文件中应该出现的内容,均在后面用注释的方式作了说明。   Notice:   本文中出现的很多字符,为了美观和直观,中英文输入法混用,或者加多个空格。大家在编程时,切记使用英文半角输入法,而且不管你加多少空格或制表符,编译器都按一个处理。
http://www.itnose.net/detail/6477880.html 2016-03-29 08:56   [C语言]

  1  2  3  4  5 下一页>