




已阅读5页,还剩23页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
指针,数组,类型的识别,参数可变的函数。一指针。它的本质是地址的类型。在许多语言中根本就没有这个概念。但是它却正是C灵活,高效,在面向过程的时代所向披靡的原因所在。因为C的内存模型基本上对应了现在von Neumann(冯诺伊曼)计算机的机器模型,很好的达到了对机器的映射。不过有些人似乎永远也不能理解指针【注1】。注1:Joel Spolsky就是这样认为的,他认为对指针的理解是一种aptitude,不是通过训练就可以达到的/pr . /fog0000000073.html指针可以指向值、数组、函数,当然它也可以作为值使用。看下面的几个例子:int* p;/p是一个指针,指向一个整数int* p;/p是一个指针,它指向第二个指针,然后指向一个整数int (*pa)3;/pa是一个指针,指向一个拥有3个整数的数组int (*pf)();/pf是一个指向函数的指针,这个函数返回一个整数后面第四节我会详细讲解标识符(identifier)类型的识别。1.指针本身的类型是什么?先看下面的例子:int a;/a的类型是什么?对,把a去掉就可以了。因此上面的4个声明语句中的指针本身的类型为:int* int*int (*)3int (*)()它们都是复合类型,也就是类型与类型结合而成的类型。意义分别如下:point to int(指向一个整数的指针)pointer to pointer to int(指向一个指向整数的指针的指针)pointer to array of 3 ints(指向一个拥有三个整数的数组的指针)pointer to function of parameter is void and return value is int (指向一个函数的指针,这个函数参数为空,返回值为整数)2.指针所指物的类型是什么?很简单,指针本身的类型去掉 “*”号就可以了,分别如下:intint*int ()3int ()()3和4有点怪,不是吗?请擦亮你的眼睛,在那个用来把“*”号包住的“()”是多余的,所以:int ()3就是int 3(一个拥有三个整数的数组)int ()()就是int ()(一个函数,参数为空,返回值为整数)【注2】注2:一个小小的提醒,第二个“()”是一个运算符,名字叫函数调用运算符(function call operator)。3.指针的算术运算。请再次记住:指针不是一个简单的类型,它是一个和指针所指物的类型复合的类型。因此,它的算术运算与之(指针所指物的类型)密切相关。int a8;int* p = a;int* q = p + 3;p+;指针的加减并不是指针本身的二进制表示加减,要记住,指针是一个元素的地址,它每加一次,就指向下一个元素。所以:int* q = p + 3;/q指向从p开始的第三个整数。p+;/p指向下一个整数。double* pd;/某些计算之后double* pother = pd 2;/pother指向从pd倒数第二个double数。4.指针本身的大小。在一个现代典型的32位机器上【注3】,机器的内存模型大概是这样的,想象一下,内存空间就像一个连续的房间群。每一个房间的大小是一个字节(一般是二进制8位)。有些东西大小是一个字节(比如char),一个房间就把它给安置了;但有些东西大小是几个字节(比如double就是8个字节,int就是4个字节,我说的是典型的32位),所以它就需要几个房间才能安置。注3:什么叫32位?就是机器CPU一次处理的数据宽度是32位,机器的寄存器容量是32位,机器的数据,内存地址总线是32位。当然还有一些细节,但大致就是这样。16位,64位,128位可以以此类推。这些房间都应该有编号(也就是地址),32位的机器内存地址空间当然也是32位,所以房间的每一个编号都用32位的二进制数来编码【注4】。请记住指针也可以作为值使用,作为值的时候,它也必须被安置在房间中(存储在内存中),那么指向一个值的指针需要一个地址大小来存储,即32位,4个字节,4个房间来存储。注4:在我们平常用到的32位机器上,绝少有将32位真实内存地址空间全用完的(232 4G),即使是服务器也不例外。现代的操作系统一般会实现32位的虚拟地址空间,这样可以方便运用程序的编制。关于虚拟地址(线性地址)和真实地址的区别以及实现,可以参考Linux源代码情景分析的第二章存储管理,在互联网上关于这个主题的文章汗牛充栋,你也可以google一下。但请注意,在C+中指向对象成员的指针(pointer to member data or member function)的大小不一定是4个字节。为此我专门编制了一些程序,发现在我的两个编译器(VC7.1.3088和Dev-C+)上,指向对象成员的指针的大小没有定值,但都是4的倍数。不同的编译器还有不同的值。对于一般的普通类(class),指向对象成员的指针大小一般为4,但在引入多重虚拟继承以及虚拟函数的时候,指向对象成员的指针会增大,不论是指向成员数据,还是成员函数。【注5】。注5:在Andrei Alexandrescu的Modern C+ Design的5.13节Page124中提到,成员函数指针实际上是带标记的(tagged)unions,它们可以对付多重虚拟继承以及虚拟函数,书上说成员函数指针大小是16,但我的实践告诉我这个结果不对,而且具体编译器实现也不同。一直很想看看GCC的源代码,但由于旁骛太多,而且心不静,本身难度也比较高(这个倒是不害怕_),只有留待以后了。还有一点,对一个类的static member来说,指向它的指针只是普通的函数指针,不是pointer to class member,所以它的大小是4。5.指针运算符&和*它们是一对相反的操作,&取得一个东西的地址(也就是指针),*得到一个地址里放的东西。这个东西可以是值(对象)、函数、数组、类成员(class member)。其实很简单,房间里面居住着一个人,&操作只能针对人,取得房间号码;*操作只能针对房间,取得房间里的人。参照指针本身的类型以及指针所指物的类型很好理解。小结:其实你只要真正理解了1,2,就相当于掌握了指针的牛鼻子。后面的就不难了,指针的各种变化和C语言中其它普通类型的变化都差不多(比如各种转型)。二数组。在C语言中,对于数组你只需要理解三件事。1C语言中有且只有一维数组。所谓的n维数组只是一个称呼,一种方便的记法,都是使用一维数组来仿真的。C语言中数组的元素可以是任何类型的东西,特别的是数组作为元素也可以。所以int a345就应该这样理解:a是一个拥有3个元素的数组,其中每个元素是一个拥有4个元素的数组,进一步其中每个元素是拥有5个整数元素的数组。是不是很简单!数组a的内存模型你应该很容易就想出来了,不是吗?:)2数组的元素个数,必须作为整数常量在编译阶段就求出来。int i;int a;/不合法,编译不会通过。也许有人会奇怪char str = “test”;没有指定元素个数为什么也能通过,因为编译器可以根据后面的初始化字符串在编译阶段求出来,不信你试试这个:int a;编译器无法推断,所以会判错说“array size missing in a”之类的信息。不过在最新的C99标准中实现了变长数组【注6】注6:如果你是一个好奇心很强烈的人,就像我一样,那么可以查看C99标准。3对于数组,可以获得数组第一个(即下标为0)元素的地址(也就是指针),从数组名获得。比如int a5; int* p = a;这里p就得到了数组元素a0的地址。其余对于数组的各种操作,其实都是对于指针的相应操作。比如a3其实就是*(a+3)的简单写法,由于*(a+3)=*(3+a),所以在某些程序的代码中你会看到类似3a的这种奇怪表达式,现在你知道了,它就是a3的别名。还有一种奇怪的表达式类似a-1,现在你也明白了,它就是*(a-1)【注7】。注7:你肯定是一个很负责任的人,而且也知道自己到底在干什么。你难道不是吗?:)所以你一定也知道,做一件事是要付出成本的,当然也应该获得多于成本的回报。我很喜欢经济学,经济学的一个基础就是做什么事情都是要花成本的,即使你什么事情也不做。时间成本,金钱成本,机会成本,健康成本可以这样说,经济学的根本目的就是用最小的成本获得最大的回报。所以我们在自己的程序中最好避免这种邪恶的写法,不要让自己一时的智力过剩带来以后自己和他人长时间的痛苦。用韦小宝的一句话来说:“赔本的生意老子是不干的!”但是对邪恶的了解是非常必要的,这样当我们真正遇到邪恶的时候,可以免受它对心灵的困扰!对于指向同一个数组不同元素的指针,它们可以做减法,比如int* p = q+i;p-q的结果就是这两个指针之间的元素个数。i可以是负数。但是请记住:对指向不同的数组元素的指针,这样的做法是无用而且邪恶的!对于所谓的n维数组,比如int a23;你可以得到数组第一个元素的地址a和它的大小。*(a+0)(也即a0或者*a)就是第一个元素,它又是一个数组int3,继续取得它的第一个元素,*(*(a+0)+0)(也即a00或者*(*a)),也即第一个整数(第一行第一列的第一个整数)。如果采用这种表达式,就非常的笨拙,所以a00记法上的简便就非常的有用了!简单明了!对于数组,你只能取用在数组有效范围内的元素和元素地址,不过最后一个元素的下一个元素的地址是个例外。它可以被用来方便数组的各种计算,特别是比较运算。但显然,它所指向的内容是不能拿来使用和改变的!关于数组本身大概就这么多,下面简要说一下数组和指针的关系。它们的关系非常暧昧,有时候可以交替使用。比如 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”如下mynamewuaihua0对于char* myname;如下表myname|/wuaihua0注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的类型是achar* p;/p的类型是char*那么请你看看下面几个:int* (*a5)(int, char*); /1void (*b10) (void (*)(); /2doube(*)() (*pa)9; /3如果你是第一次看到这种类型声明的时候,我想肯定跟我的感觉一样,就如晴天霹雳,五雷轰顶,头昏目眩,一头张牙舞爪的狰狞怪兽扑面而来。不要紧(Take it easy)!我们慢慢来收拾这几个面目可憎的纸老虎!1C语言中函数声明和数组声明。函数声明一般是这样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 a5;对于数组指针(pointer to array)的声明是这样:int (*pa)5; 你也必须习惯。可以这样使用:pa = &a;/ 赋值(assignment)操作int i = (*pa)2/将a2赋值给i;2.有了上面的基础,我们就可以对付开头的三只纸老虎了!:)这个时候你需要复习一下各种运算符的优先顺序和结合顺序了,顺便找本书看看就够了。1:int* (*a5)(int, char*);首先看到标识符名a,“”优先级大于“*”,a与“5”先结合。所以a是一个数组,这个数组有5个元素,每一个元素都是一个指针,指针指向“(int, char*)”,对,指向一个函数,函数参数是“int, char*”,返回值是“int*”。完毕,我们干掉了第一个纸老虎。:)2:void (*b10) (void (*)();b是一个数组,这个数组有10个元素,每一个元素都是一个指针,指针指向一个函数,函数参数是“void (*)()”【注10】,返回值是“void”。完毕!注10:这个参数又是一个指针,指向一个函数,函数参数为空,返回值是“void”。3. doube(*)() (*pa)9; pa是一个指针,指针指向一个数组,这个数组有9个元素,每一个元素都是“doube(*)()”【也即一个指针,指向一个函数,函数参数为空,返回值是“double”】。现在是不是觉得要认识它们是易如反掌,工欲善其事,必先利其器!我们对这种表达方式熟悉之后,就可以用“typedef”来简化这种类型声明。1:int* (*a5)(int, char*);typedef int* (*PF)(int, char*);/PF是一个类型别名【注11】。PF a5;/跟int* (*a5)(int, char*);的效果一样!注11:很多初学者只知道typedef char* pchar;但是对于typedef的其它用法不太了解。Stephen Blaha对typedef用法做过一个总结:“建立一个类型别名的方法很简单,在传统的变量声明表达式里用类型名替代变量名,然后把关键字typedef加在该语句的开头”。可以参看程序员杂志2001.3期C+高手技巧20招。 2:void (*b10) (void (*)();typedef void (*pfv)();typedef void (*pf_taking_pfv)(pfv);pf_taking_pfv b10; /跟void (*b10) (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是constconst char*;/char是constchar* const;/*(指针)是constconst char* const;/char和*都是const对初学者,const char*;和 char* const;是容易混淆的。这需要时间的历练让你习惯它。上面的声明有一个对等的写法:const在后面int const;/int是constchar const*;/char是constchar* const;/*(指针)是constchar 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:*Thats the difference!如果你还是不明白,可以去看一下hash是如何实现的!(2)The name iota is taken from the programming language APL.而APL语言主要是做数学计算的,在数学中有很多公式会借用希腊字母,希腊字母表中有这样一个字母,大写为,小写为,它的英文拼写正好是iota,这个字母在(theta)和(kappa)之间!你可以/wiki/APL_programming_language下面有一段是这样的: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:你可以在Herb Sutter的More Exceptional C+中的条款20:An Unmanaged Pointer Problem, Part 1:Parameter Evaluation找到相关的细节论述。(2)_cdecl表示调用端负责被调用端引数的压栈和出栈。参数可变的函数采用的是这种调用形式。为什么这种函数要采用不同于前面的调用形式呢?那是因为_stdcall调用形式对它没有作用,被调用端根本就无法知道调用端的引数个数,它怎么可能正确工作?所以这种调用方式是必须的,不过由于参数参数可变的函数本身不多,所以用的地方比较少。对于这两种方式,你可以编制一些简单的程序,然后反汇编,在汇编代码下面你就可以看到实际的区别,很好理解的!重载函数有很多匹配(match)规则调用。参数为“”的函数是匹配最低的,这一点在Andrei Alexandrescu的惊才绝艳之作Modern C+ Design中就有用到,参看Page34-35,2.7“编译期间侦测可转换性和继承性”。后记:C语言的细节肯定不会只有这么多,但是这几个出现的比较频繁,而且在C语言中也是很重要的几个语言特征。如果把这几个细节彻底弄清楚了,C语言本身的神秘就不会太多了。C语言本身就像一把异常锋利的剪刀,你可以用它做出非常精致优雅的艺术品,也可以剪出一些乱七八糟的废纸片。能够将一件武器用到出神入化那是需要时间的,需要多长时间?不多,请你拿出一万个小时来,英国Exter大学心理学教授麦克.侯威专门研究神童和天才,他的结论很有意思:“一般人以为天才是自然而生、流畅而不受阻的闪亮才华,其实,天才也必须耗费至少十年光阴来学习他们的特殊技能,绝无例外。要成为专家,需要拥有顽固的个性和坚持的能力每一行的专业人士,都投注大量心血,培养自己的专业才能。”【注16】注16:台湾女作家、电视节目主持人吴淡如拿出一万个小时来。读者2003.1期。“不用太努力,只要持续下去。想拥有一辈子的专长或兴趣,就像一个人跑马拉松赛一样,最重要的是跑完,而不是前头跑得有多快。”推荐两本书:K&R的The C Programming language,Second Edition。Andrew Konig的C陷阱与缺陷。本文从中引用了好几个例子,一本高段程序员的经验之谈。但是对纯粹的初学者不太合适,如果你有一点程序设计的基础知识,花一个月的时间好好看看这两本书,C语言本身就不用再花更多的精力了第一章概述1 C语言的特点语言简洁、紧凑,使用方便、灵活。共有个关键字,种控制语句。运算符丰富,公有种运算符。数据结构丰富,数据类型有:整型、实型、字符型、数组、指针、结构体、共用体等。具有结构化的控制语句(如ifelse、while、dowhile、switch、for)语法限制不太严格,程序设计自由度大。允许直接访问物理地址,能进行位(bit)操作,可以直接对硬件操作。生成目标代码质量高,程序执行效率高。可移植性好。2 C语言的用途C虽不擅长科学计算和管理领域,但对操作系统和系统实用程序以及对硬件进行操作方面,C有明显的优势。现在很多大型应用软件也用编写。第二章数据类型、运算符与表达式1 C的数据类型C的数据类型包括:整型、字符型、实型或浮点型(单精度和双精度)、枚举类型、数组类型、结构体类型、共用体类型、指针类型和空类型。2 常量与变量常量其值不可改变,符号常量名通常用大写。变量其值可以改变,变量名只能由字母、数字和下划线组成,且第一个字符必须为字母或下划线。否则为不合法的变量名。变量在编译时为其分配相应存储单元。3 整型数据整型常量的表示方法:十进制不用说了,八进制以0开头,如0123,十六进制以0x开头,如0x1e。整型变量分为:基本型(int)、短整型(short int)、长整型(long int)和无符号型。不同机器上各类数据所占内存字节数不同,一般int型为个字节,long型为4个字节。4 实型数据实型常量表示形式:十进制形式由数字和小数点组成(必须有小数点),如:0.12、.123、123.、0.0等。指数形式如123e3代表12310的三次方。实型变量分为单精度(float)和双精度(double)两类。在一般系统中float型占4字节,7位有效数字,double型占8字节,1516位有效数字。5 字符型数据字符变量用单引号括起来,如a,b等。还有一些是特殊的字符常量,如n,t等。分别代表换行和横向跳格。字符变量以char 来定义,一个变量只能存放一个字符常量。字符串常量是由双引号括起来的字符序列。这里一定要注意a和a的不同,前者为字符常量,后者为字符串常量,c规定:每个字符串的结尾加一个结束标志0,实际上a包含两个字符:a和0。6 数值型数据间的混合运算整型、字符型、实型数据间可以混合运算,运算时不同类型数据要转换成同一类型再运算,转换规则:char,short - int - unsigned - long - double = = != )逻辑运算符( ! & | )位运算符( | & )赋值运算符(= )条件运算符(? : )逗号运算符( , )指针运算符( * & )求字节数( sizeof )强制类型转换(类型)分量运算符(. - )下标运算符( )其它运算符(如函数调用运算符( ) )自增自减运算符(+ - )注意:+i和i+的不同之处,+i使用i之前先使i加,i+使用i之后,使i加。逗号表达式的求解过程:先求解表达式,再求解表达式,整个表达式的值是表达式的值。第三章 最简单的程序设计c的种控制语句:if() elsefor()while()dowhile()continuebreakswitchgotoreturn程序的三种基本结构:顺序结构,选择结构,循环结构数据输出c语言不提供输入输出语句,输入输出操作是由c的库函数完成。但要包含头文件stdio.h。putchar( ) 向终端输出一个字符printf( )的格式字符: d格式符用来输出十进制整数%d 按整型数据的实际长度输出%md 使输出长度为m,如果数据长度小于m,则左补空格,如果大于m,则输出实际长度%ld 输出长整型数据 o格式符以八进制形式输出整数 x格式符以十六进制形式输出整数 u格式符用来输出unsigned型数据,以十进制形式输出 c格式符用来输出一个字符 s格式符输出一个字符串%s输出实际长度字符串%ms 输出的串占m列,如果串长度小于m,左补空格,如果大于m,实际输出%-ms输出的串占m列,如果串长度小于m,右补空格,%m.ns 输出占m列,但只取字符串中左端n个字符并靠右对齐%-m.ns m、n含义同上,靠左对齐,如果nm,则m自动取n值 f格式符以小数形式输出实数%f 整数部分全部输出,小数部分输出6位%m.nf 输出数据共占m列,其中有n位小数。如果数值长度小于m,左补空格%-m.nf 同上,右补空格 e格式符以指数形式输出实数%e 系统指定位小数,5位指数(e+002 ) g格式符输出实数,根据数值大小,自动选f格式或e格式3数据输入getchar( ) 从终端输入一个字符scanf( 格式控制,地址列表) 标准C scanf中不使用%u,对于unsigned型数据,以%d或%o或%x输入。后的*,用来跳过它相应的数据。输入数据时不能规定精度如scanf( %7.2f, &a );是不合法的。第四章逻辑运算和判断选取控制1 关系运算符:c提供种关系运算符( = = != )前四种优先级高于后两种。2 If语句C提供了三种形式的if语句If(表达式) 语句If(表达式) 语句1 else 语句2If(表达式1) 语句1Else if(表达式2) 语句2else 语句n3 条件运算符(ab)?a:b 条件为真,表达式取值a,否则取值b4 Switch语句Switch(表达式)case 常量表达式:语句; break;case 常量表达式:语句2; break; case 常量表达式n:语句; break;default :语句;第五章 循环控制1 几种循环语句goto语句(现已很少使用)while语句先判断表达式后执行语句do-while语句先执行语句后判断表达式for语句2 Break语句和continue语句Break语句用于跳出循环,continue用于结束本次循环。第六章 数组1 一维数组c规定只有静态存储(static)和外部存储(extern)数组才能初始化。给数组初始化时可以不指定数组长度。2 二维数组3 字符数组部分字符串处理函数puts(字符数组)将一个字符串输出到终端。gets(字符数组) 从终端输入一个字符串到字符数组,并且得到一个函数值,为该字符数组的首地址strcat(字符数组,字符数组2)连接两个字符数组中的字符串,数组1必须足够大。Strcpy(字符数组,字符串2)将字符串拷贝到字符数组中。Strcmp(字符串1,字符串2) 比较字符串,相等返回0,字符串字符串2,返回正数,小于返回负数。Strlen(字符数组) 求字符串长度。Strlwr( 字符串)将字符串中的大写字母转换成小写Strupr( 字符串) 将字符串中的小写字母转换成大写以上是一些比较常用的字符串处理函数。第七章 函数1 关于形参和实参的说明 在函数被调用之前,形参不占内存 实参可以是常量、变量或表达式 必须指定形参的类型 实参与形参类型应一致 实参对形参的数据传递是值传递,即单向传递2 函数返回值如果想让函数返回一个值,在函数中就要用return语句来获得,在定义函数时也要对函数值指定类型,如果不指定,默认返回整型。3 函数调用1)注意在函数调用时实参和形参的个数、类型应一一对应。对实参表求值的顺序是不确定的,有的系统按自左至右,有的系统则按自右至左的顺序。这一点要注意。2)函数调用的方式:函数语句,函数表达式,函数参数3)如果主调函数和被调函数在同一文件中,并且主调函数在前,那么一般要在主调函数中对被调函数进行说明。除非:(1)被调函数的返回值类型为整型或字符型(2)被调函数出现在主调函数之前。4)对函数的说明和定义是不同的,定义是指对函数功能的确立,包括指定函数名,函数值类型,形参及其类型、函数体等。说明则只是对已定义的函数返回值类型进行说明,只包括函数名、函数类型以及一个空的括弧,不包括形参和函数体。5)c语言允许函数的递归调用(在调用一个函数的过程中又出现直接或间接的调用该函数本身)。4 数组作为函数参数1)数组元素作为函数参数和一般变量相同2)数组名作参数应该在主调和被调函数分别定义数组,形参数组的大小可以不定义。注意:数组名作参数,不是单向传递。3)多维数组作参数,在被调函数中对形参数组定义时可以省略第一维的大小说明,但不能省略第二维或更高维的说明。5 局部变量和全局变量从变量作用域角度分,变量可分为局部变量和全局变量。1)内部变量(局部变量)在一个函数内定义,只在函数范围内有效的变量。 2)外部变量(全局变量)在函数外定义,可以为本文件其它函数所共用,有效范围从定义变量的位置开始到本文件结束。建议尽量少使用全局变量,因为它在程序全部执行过程中都占用资源,而且使函数的通用性降低了。如果在定义外部变量之前的函数要想使用该外部变量,则应在该函数中用extern作外部变量说明。6 动态存储变量与静态存储变量从变量值存在的时间(生存期)角度来分,可分为静态存储变量和动态存储变量。静态存储指在程序运行期间给变量分配固定的存储空间,动态存储指程序运行期间根据需要动态的给变量分配存储空间。C语言中,变量的存储方法分为两大类:静态存储类和动态存储类,具体包括:自动的(auto),静态的(static),寄存器的(register),外部的(extern)。1) 局部变量的存储方式函数中的局部变量如不作专门说明,都之auto的,即动态存储的,auto可以省略。局部变量也可以定义为static的,这时它在函数内值是不变的。静态局部变量如不赋初值,编译时系统自动赋值为,动态局部变量如不赋初值,则它的值是个不确定的值。C规定,只有在定义全局变量和局部静态变量时才能对数组赋初值。为提高执行效率,c允许将局部变量值放在寄存器中,这种变量叫register变量,要用register说明。但只有局部动态变量和形式参数可以作为register变量,其它不行。2) 全局变量的存储方式全局变量在函数外部定义,编译时分配在静态存储区,可以在程序中各个函数所引用。多个文件的情况如何引用全局变量呢?假如在一个文件定义全局变量,在别的文件引用,就要在此文件中用extern对全局变量说明,但如果全局变量定义时用static的话,此全局变量就只能在本文件中引用了,而不能被其它文件引用。3) 存储类别小结从作用域角度分,有局部变量和全局变量局部变量:自动变量,即动态局部变量(离开函数,值就消失)静态局部变量(离开函数,值仍保留)寄存器变量(离开函数,值就消失)(形参可定义为自动变量和寄存器变量)全局变量:静态全局变量(只限本文件引用)全局变量(允许其它文件引用)从存在的时间分,有静态存储和动态存储动态存储:自动变量(本函数内有效)寄存器变量(本函数内有效)形参静态存储:静态局部变量(函数内有效)静态全局变量(本文件内有效)全局变量(其它文件可引用)从变量值存放的位置分静态存储区:静态局部变量静态全局变量全局变量动态存储区:自动变量和形参寄存器内:寄存器变量7 内部函数和外部函数内部函数:只能被本文件中的其它函数调用,定义时前加static,内部函数又称静态函数。外部函数:可以被其它文件调用,定义时前加extern,如果省略,则隐含为外部函数,在需要调用此函数的文件中,一般要用extern说明。第八章 预编译处理c编译系统在对程序进行通常的编译之前,先进行预处理。c提供的预处理功能主要有以下三种:1)宏定义2)文件包含3)条件编译1 宏定义不带参数的宏定义用一个指定的标识符来代表一个字符串,形式:#define 标识符 字符串几点说明:) 宏名一般用大写)宏定义不作语法检查,只有在编译被宏展开后的源程序时才会报错) 宏定义不是c语句,不在行末加分号) 宏名有效范围为定义到本源文件结束) 可以用#undef命令终止宏定义的作用域
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 供热知识培训课件
- 长期假期后管理办法
- 企业用电安全培训下载课件
- 疫情防治宣传管理办法
- 生物材料共享管理办法
- 软实力竞争策略构建-洞察及研究
- 加密保护技术-洞察及研究
- 认证系统抗攻击设计-洞察及研究
- 路运网络脆弱性分析-洞察及研究
- 出国前安全教育培训课件
- 2025春季学期国开电大本科《管理英语3》一平台在线形考综合测试形考任务试题及答案
- 医疗机构水电气设备维护流程
- 数据的形式与记录载体(教学设计)2024-2025学年清华版信息技术四年级上册
- 合规管理战略规划范文
- 餐饮服务与数字化运营 习题及答案 项目一
- 消毒隔离的管理
- 纪委执纪场所审查谈话系统解决方案
- 两办意见、《条例》、八项硬措施、治本攻坚三年行动方案学习课件
- SuperKids1-第一单元测试卷-Unit-1-Unit-3
- 基于知识图谱技术的计算机网络链路漏洞检测研究
- 中华人民共和国各级人民代表大会常务委员监督法宣贯培训2024
评论
0/150
提交评论