传智播客C语言入门5_第1页
传智播客C语言入门5_第2页
传智播客C语言入门5_第3页
传智播客C语言入门5_第4页
传智播客C语言入门5_第5页
已阅读5页,还剩197页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

传智播客C语言入门教程(5),讲师:尹成QQ:77025077博客:,C语言,C+语言,传智播客,高薪就业,2.同一类型多个元素的集合简单数组,3.C语言难点指针初探,4.字符串及字符串操作,5.结构体、共用体、枚举和typedef,传智播客C语言入门教程(5)大纲,1.写程序就是写函数-函数入门,C语言课程概述,6.初学者的疑难解答,5.1函数入门,大家都大致都了解一点数学意义上“函数”的概念,比如“y=f(x)”,且不论f的具体形式如何,其基本特点是“对一个x,有一个y值与之对应”。C语言中,“函数”是个重要的概念,是模块化编程的基础。本小节主要涉及函数的概念、函数原型、函数的定义、函数的参数传递机制等相对基础的内容,为后面进一步阐述模块化编程打下基础。,5.1.1什么是函数?根据输入进行处理返回输出,代码编多了会发现一个问题:一些通用的操作,比如交换两个变量的值,对一组变量进行排序等,可能在多个程序中都会用到,不仅如此,在单独一个程序中也可能会对某个代码段执行多次。问题:有必要在每次执行时都把该代码段书写一次么,这不仅会让程序变得很长,且会造成难以理解,可读性下降。,5.1.2为什么使用函数呢,voidmain():x=x*x*x;y=y*y*y;z=z*z*z;ans1=x+y+z;a=a*a*a;b=b*b*b;c=c*c*c;ans2=a+b+c;:,重复多次的同一计算类型,voidmain():ans1=cube(x,y,z);ans2=cube(a,b,c);:,intcube(inta,b,c)intans;ans=(a*a*a)+(b*b*b)+(c*c*c);returnans;,函数,主程序,ans,函数可以把相对独立的某个功能抽象出来,使之成为程序中的一个独立实体。可以在同一个程序或其他程序中多次重复使用,5.1.3函数的分类,无参函数通常用来执行一些功能比较固定单一的语句。例如:getcharar(),有参函数通常通过处理传递过来的参数,将函数值返回给调用处。如:sqrt(4)、pow(2,3)、strlen(“hello”);,5.1.4函数的分割,C语言将程序按功能分割成一系列的小模块,所谓“小模块”,可理解为完成一定功能的可执行代码块,称之为“函数”。函数是C语言源程序的基本功能单位,打个比方,可以将函数视为一个黑盒子,或“加工设备”,从一头输入数据(原材料),从另一头就可以得到结果(产品)。至于函数内部是如何工作的,外部并不关心。C语言源程序均是由函数组成的,在前面给出的示例代码,只有一个main函数,这仅适用于比较简单的问题,实际上的程序往往由多个程序组成。函数的调用是由另一个函数发起的,举例来说,在A函数中调用B函数,从B函数的角度上说,A函数可视为外部函数(有的书中也叫外部程序、主调函数,B函数相应地称为被调函数),外部函数A对函数B是如何定义的,功能是如何实现的毫不关心,A对B所知道的仅限于输入给B什么,以及B会输出什么。,5.1.5库函数与自定义函数,库函数:由语言系统提供;用户无须定义,也不必在程序中作类型说明;只需在程序前包含有该函数定义的头文件;,自定义函数:用户在程序中根据需要而编写的函数;,5.1.6库函数与自定义函数,为方便解决某些基本问题,C语言提供了库函数,库函数是将前人书写的、有通用功能的函数打包,方便开发者调用,或是一些复杂的功能,比如输入输出,这涉及到硬件方面的内容,如果要我们自己写如何输出输出的函数,肯定要头大半天。C语言中的库函数十分丰富,大致可分为“标准库函数”和“第三方库函数”两类,标准库函数是得到广泛认可,形式统一,被多种类的编译器支持的库函数,而第三方库函数是一些软件厂商为某些特定功能领域开发,多具有专用性。随着C语言的不断发展和应用领域的拓展,标准化的工作也在不断深入,标准库函数也会不断扩充。除了库函数外,C语言允许用于自定义函数以灵活解决各种问题,用户可以将自己的算法编成一个个相对独立的函数模块,用调用的方法来使用函数。某种程度上说,C语言的全部功能是由这样那样的函数来实现的,C语言也常称为“函数式语言”。,5.1.7常用的库函数,5.1.8自定义函数,函数的调用可能是由另一个函数触发,但函数的定义都是平行的,包括main函数在内,所谓“平行”,有两层含义,一是“不允许把一个函数定义在另一个函数内”,这说明,函数定义都要在main函数外部,二是“不同函数定义放置位置没有关系”,可以定义在main函数前,也可以定义在main函数之后。,5.1.9函数定义的语法,和变量一样,要想使用一个函数,定义是不可缺少的,函数定义有4个要素:参数列表,返回类型,函数名和函数体,参数列表和返回类型对应着输入输出,函数名用于和程序中其他程序实体区分,而函数体是一段可执行的代码块,实现特定的算法或功能。函数的基本定义语法如下:返回类型函数名(参数列表)函数体;(1)输入:参数列表基本形式为:类型变量名1,类型变量名2,类型变量名3,(2)输出:返回类型返回类型用于指明函数输出值的类型,如果没有输出值,返回类型为void。如果在函数定义时没有注明返回类型,默认为int。(3)函数名函数名用于标示该函数,和其他函数区分开来,因此,函数名必须是合乎编译器命名规则的标识符。参数列表、返回类型和函数名总体称为函数头,与之对应的是函数体。(4)函数体函数体是一段用于实现特定功能的代码块,比如局部变量声明和其他执行语句等。注意,在函数体内声明的变量不能和参数列表中的变量同名。,例:floatmax(floata,floatb)return(ab)?a:b;,5.1.10函数先定义再使用,定义一个函数是为了调用,函数调用有两种类型,一是“先定义,后调用”,这要求函数定义和调用语句在同一个文件内,编译器能从函数定义中提取函数的参数列表、输出类型等接口信息。二是“函数声明+函数调用”,大多数情况下,函数的定义与函数的调用并不在一个文件内,即使在一个文件中也有可能调用在前而定义在后,这时需要在调用之前先对函数声明,告诉编译器有这么一个函数存在,函数原型声明将在后面讨论,下面来看一个先定义、后调用的例子,注意:函数的定义在程序中都是平行的,即不允许在一个函数的内部再定义另一个函数;函数名是用户自定义标识符,当函数值为整型时类型名可省略(不推荐省略);当函数不需要向调用处返回值时,使用void类型名形参表中的形参是用户自定义标识符,没有参数时,圆括号不能省略,此时函数为无参函数。,5.1.11实际参数与形式参数,main()floatx=max(3,4);,floatmax(floata,floatb)return(ab)?a:b;,调用max时给出了实际参数。,定义max时使用了形式参数。,实际参数、形式参数,调用函数,被调函数,通过参数传递传入值,通过return返回值,注意:参数传递是单向值传递。,5.1.12函数的形参与实参,在函数定义时参数列表中是a和b,而在函数调用时传递进来的参数是num1和num2,这两种参数是什么关系呢?打个形象的比方,这是角色和演员的关系。函数定义时列表中的参数称为形参,是“剧本角色”,而函数调用时传递进来的参数称为实参,是“演员”,函数执行的过程就是演戏的过程。程序刚开始执行的时候,编译器并不为形参分配存储空间,因为它只是个角色,不是实体,一直要到函数调用时,编译器为形参分配存储空间,并将实参的值复制给形参,结合。可知,在“floatlast=max(num1,num2)语句调用前,a和b都不是真正的程序变量,一直到max函数被调用,a和b才被创建,并分别用num1和num2为其赋值,找这种情况下,在函数内对a和b的处理并不影响num1和num2,这类似于“某个演员扮演的角色在戏中受伤,并不是说演员真的受伤了”,而且,在函数执行结束返回时,创建的形参被撤销,这类似于“戏演完了,剧中角色自然也就停止了”。,5.1.13参数传递说明,注意:(1)实参向形参的数据传递是单向的“值传递”。实参和形参在内存中被分配到不同的单元,发生函数调用时,只是将实参的“值“传给形参,在函数中即使改变了形参的值也不会影响实参原来的值(2)实参和形参的顺序应该一致且个数相等。在调用函数时,如果实参顺序与函数定义的形参顺序不一致,形参可能无法接收到正确的值;如果实参与形参个数不一致,编译器会报错(3)实参和形参的数据类型应尽量保持一致。,5.1.14数据类型转换延伸,函数调用时,编译器通过函数定义或声明知道形参类型,如果用户传递给被调用函数的实参不满足类型要求,则会产生类型转换,将实参转换成形参类型。,5.1.15函数的返回值,使用return语句,可以使函数向调用处返回一个值。,它有两个功能:(1)立即从所在的函数体中退出,返回到调用它的程序中去,(2)同时返回一个值给调用它的函数。,有两种常用方法可以让函数终止运行并返回到调用它的函数中去:(1)当执行到函数的最后一条语句后返回(2)当执行到语句return时返回。,5.1.16函数的返回值,(1)如函数类型被定义成void以外的任何一种类型,则函数内部必须出现return语句(2)return语句中表达式的类型应与函数值类型一致。若不一致时,则以函数值的类型为准。,注意:,intmax(floatx,floaty)returnxy?x:y;,intmain()floatx=0.5,y=0.3,z;z=max(x,y);printf(“max=%f”,z);return0;,return语句返回float类型,而max函数被定义为int类型,返回值类型被强制转换为int,5.1.17函数返回,bigger函数定义如下:intbigger(inta,intb)/*函数头,返回类型函数名(参数列表)*/if(ab)returna;/*返回值*/elsereturnb;既然说a和b都是实参,在程序被调用时方才创建,程序退出时便被撤销,那诸如“returna;”之类的返回语句岂不是没有意义,返回的是一个被撤销的量?函数的返回机制应如何理解呢。理解的关键词是“复制”,执行到return语句时,return的值被复制到某个内存单元或寄存器中,其地址是由编译器来维护的,我们不用操心,也就是说,在a和b被撤销前,返回的值(a或b)被复制保存到了某个地方,编译器访问该内存单元即可知道函数的返回值,下述语句:intres=bigger(num1,num2);/*函数调用,值的返回*/,5.1.18函数调用,下列几种调用形式都是正确的:函数以表达式语句的形式出现。如:print_line();函数出现在表达式中。v2=max(x,y)*10;函数作其它函数的实参。num=max(max(max(a,b),c),d);,5.1.19函数调用,floatmax(floata,floatb)return(ab)?a:b;voidmain()max(3.1,5.6);,之前已经有了函数定义,函数和变量一样,也必须“先定义,后使用”。下面定义的max函数就放在了主函数中调用语句之前,所以主函数可以直接调用max。,5.1.20函数调用,intmain()floata,b;scanf(“%f%f”,要调用在后面定义的函数,必须在调用函数之前提前声明此函数的原型,否则无法识别函数。原型可不写出形参名。,5.1.21函数调用注意事项,函数调用时需注意的一些常见问题:,有参函数在调用时,实参与形参个数必须相等,类型应尽量一致;如sqrt(4,3)sqrt(4),即便是在调用时无参函数,括号也不能省略。如getcharar();,调用库函数时,应在程序头部用include宏命令把相关的头文件包含进来。如#include,5.1.22函数声明语法,(“编译器如何认识printf函数”)已经简要介绍了函数原型声明的内容,是否还记得这段代码:最上面两条语句即是函数原型声明语句,调用函数之前,一定要使得编译器知道函数原型,这样编译器才知道有哪些函数名,该函数需要些什么样类型的参数,返回什么样类型的数值。函数原型声明的基本形式如下:返回类型函数名(数据类型形参,数据类型形参,数据类型形参,);或返回类型函数名(数据类型,数据类型,数据类型),5.1.23声明不同于定义,函数的声明和定义不同,总的来说有以下几点:(1)函数定义4个要素不可或缺,是一个完整的函数实现形式,包括返回类型、形参、函数名和最重要的函数体的定义,而函数声明被用来通知编译器被调函数的返回类型、名称和参数类型信息,相当于“接口”,声明时没有函数体而且形参的类型是关心的要点,而形参的名称在声明时可省略。(2)在某些情况下,函数的声明可以省略,如函数先定义,后调用的情况,但函数的定义不能省略,且只能定义一次,而且,定义要比声明更为复杂和完整。(3)函数定义结束时不用加分号,而声明结束时必须加分号,因此,直接在函数头后添上分号作为函数的声明是个不错的方法。特别强调:函数的原型声明应与函数头保持一致,否则,编译器会报错。,5.1.24函数的调用过程,C语言是由函数组成的,已经介绍了函数的定义、声明和调用等基础知识,下面来看一下函数的调用过程,即模块间是如何配合的,5.1.25函数的调用,通过在程序中使用函数名称,可以执行函数中包含的语句,这称为调用函数函数之间允许相互调用,也允许嵌套调用函数还可以自己调用自己,称为递归调用,#includevoidmain():set_discount();displayDiscount();:,floatset_discount():floatdisplayDiscount():,5.1.26函数调用,voidreverse():,#includevoidmain():palindrome();:,voidpalindrome():reverse();:,从一个函数调用另一个函数称为函数的嵌套调用,5.1.27函数调用,/*此函数用于计算a的阶乘*/intfactorial(inta)if(a=1)return1;elsea=a*factorial(a-1);returna;,在一个函数体内调用自身称为函数的递归调用,5.1.28面向过程的程序结构,在60年代计算机发展的初期,程序设计是少数聪明人的玩具,程序员可以根据自己的喜好,像捏泥巴一样进行程序设计,注释几乎是一行没有,想到哪写到哪,大多数程序代码组织混乱,可以说只有作者本人可以看懂,有的甚至作者读起来也不知所以,常称为被称为“意大利面条式编程”。这种个人英雄主义的单打独斗在解决小规模问题时勉强可以,但程序规模的不断扩大,一大堆的问题凸现出来:程序质量低下,进度延误,预算严重超支,这就是“软件危机”,给程序开发的前景蒙上了一层暗淡的色彩。结构化程序设计方法就是在这个背景下提出的,除了前面章节讲过的3种控制结构:顺序、分支和循环外,结构化程序设计的另一个关键概念是模块化设计。,5.1.29面向过程的模块化编程,生活中常常接触到模块化的概念,模块化程序设计大致有点像小时候玩的积木游戏,用木块组合的方式很容易地就构筑起了“大厦”,模块化至少有两点好处:一是封装,“积木块”是“基本砖块”的组合,对外是个整体,使用方便,二是可复用,“柱子”封装好后,既可以用在这个建筑上,又可以用在那个建筑上。程序设计也可以借鉴这一思想,用模块化的方法进行程序设计,函数正是模块化方法的体现。虽说语句是C语言的基本单位,但从程序设计总体把握上来看,将函数视为一个整体,大大降低了问题的复杂程度。在解决复杂问题时,首先考虑的是问题的概貌,而不是微小细节,这是人的思维和行动习惯,程序设计也是如此,先将问题分割成一个个函数,每个函数实现特定的功能,确定函数之间的联系和依赖关系,这是从整体解决某个问题。其次才是考虑每个函数应怎么写,算法流程怎么走这些问题,这就是“分而治之、逐步求精“的设计方法学。,5.1.30函数小结,函数是C语言中最重要的概念之一,如果要画张关系图的话,函数绝对是要摆放在中心位置处的,在稍后会讲到的数组、结构、指针等等,都和函数有着密切的联系,彼此搭配才能写出高效的C程序。函数有库函数和自定义函数之分不管是库函数和自定义函数,在调用前都应对其进行原型声明,声明实际上是用以通知编译器该函数的存在,方便编译。介绍了模块化编程的基本思想,实现封装与再次使用。,1,2,3,设计1个函数,判断整数有多少位,实现1+2+3+4+5+6+100的递归函数,设计一个函数,检测一个数是否为质数,5.1.31习题,5.2同一类型多个元素的集合-数组,在实际的程序设计和代码编写中,经常会用到大批同类型的数据,比如某个班学生的成绩等,为方便解决这类问题,C语言提供了数组这一数据结构,这里的数据结构,可理解为数据的存放和管理方式。和普通变量一样,在使用数组前必须先对其声明以开辟所需要的内存空间,由于数组是很多数据的集合,这些数据对应的内存单元是如何排列的,这都是本结要解决的内容。,5.2.1什么是数组,程序经常使用同类型的数据,比如要处理某个班级的学生成绩信息,如果只有几个学生,我们可以使用几个同类型变量,比如:intmark0,mark1,mark2,mark3,mark4;这样,便可以存放5个学生的成绩,但如果是几百人呢?要一直这么写下去么,如果大家觉得继续写下去没什么不妥的话,那几千甚至几万人呢,所以,如何合理组织大量同类数据是个问题。合理组织的含义包括:(1)为每个数据分配存储空间。(2)每个数据应当有唯一的标识符进行读写和查找。在这种应用背景下,数组应用而生,成功地解决了上述问题。,5.2.2为什么要使用数组,数组的元素,容器中保存的物品,日常生活中的容器,程序中的数组,5.2.3C语言中的数组,38,3210,Rate4,数组名,下标标明了元素在数组中的位置,数组元素,下标,数组大小,数组是可以在内存中连续存储多个元素的结构数组中的所有元素必须属于相同的数据类型,5.2.4数组是一大片连续内存空间,声明一个数组时,编译器为数组分配内存存储空间,值得注意的是:数组占据的内存空间是连续的,这样,很容易计算数组占据的内存大小和每个元素对应的内存首地址,举例来说,对一个大小为N,类型为short的数组,其占据的内存大小为:N*sizeof(short)=N*2如果说第1个元素在内存中的地址为p,那么第M个元素(M不大于N)在内存中的地址可表示为:p+(M-1)*sizeof(short)这充分体现了数组的有序性。,5.2.5数组类型,5.2.6一维数组,一维数组也称向量,用以组织具有一维顺序关系的一组同类型数据,在使用数组前,必须先声明数组,编译器根据声明语句为其分配内存,这样数组才有意义。要在内存中开辟一块连续内存给数组用,需要考虑以下问题,一是在哪里开辟,而是开辟多大的地方,C语言中,这都是由编译器自动完成的,编程人员说要做的是“提要求”,即所开辟的数组应能盛放多少个元素,每个元素是什么类型,另外,编程人员还要指定数组名。一维数组声明的基本格式为:类型数组名数组元素个数;比如,声明语句:doublesz6;告诉编译器3条信息:数组名是sz,存放的元素是double型,数组存放的元素个数为6,这样,便可以对数组及数组元素进行读写访问。要防止下标越界的错误发生,对上面声明的数组sz来说,有效的下标是0到5,在程序中如果出现了sz6,编译器有时并不会报错,但这可能引起程序的崩溃。,5.2.7一维数组的声明详解,datatypearrayNamesize;,类型说明符int、char、float,数组名,常量表达式:数组大小,intnum50;charlist_of_initials20;doublepressure_level6;,#defineLIMIT20.intemp_codesLIMIT;,5.2.8数组的初始化说明,存在潜在的安全隐患:没有对数组元素初始化,因为立即采用键盘输入为数组元素赋了值,貌似问题不大,但如果需要对代码修改,不小心在赋值前便使用了数组元素,这时,因为初始化时内存单元内容的不确定,程序输出的结果往往是不可预料的。不仅仅是数组,在声明创建一个变量后马上对其初始化是个良好的习惯,能有效减少各种意想不到的错误。,括号中可以省略数组宽度。此时,编译器通过给出的数据个数来定义数组的宽度;,如:inta=10,9,8,7,6,5,4,3,2,1;等价于:inta10=10,9,8,7,6,5,4,3,2,1;,一维数组的初始化形式一般为:类型数组名宽度=初值列表;如:inta10=10,9,8,7,6,5,4,3,2,1;,判断下列的初始化是否等价?intb=1,2,3,4;intb10=1,2,3,4;,5.2.9一维数组的初始化说明,intemp_code5=1299,1499,1699,1899,2099;,emp_code,其他的初始化情况:intarr10=10,9,8,7,6,5,4,3,2,1,0;/错误!越界了intarr10=9,8,7,5;/正确,后面的6个元素未初始化intarr=9,8,7;/正确:元素个数为3intarr=;/错误,到底是几个元素?inta10=1,2,3,4,5;/只给前面5个元素赋初值,后5个元素值为0或inta5=0;,5.2.10不合法的数组操作,数组对应着一片内存区域,从较高层次上看,数组可以看成是一个特殊的大“变量”,已经学过,同类型的变量之间可以相互赋值,可以比较大小,可以作运算,那数组可否进行这些操作呢,答案是否,即使是同类型、同样大小的数组,下列操作也是非法的:(1)用一个已经初始化的数组对另一个数组赋值,即使是元素类型相同,数组大小相同,这样的用法也是不允许的。intx3=7,8,9;inty3;y=x;/*错误*/(2)对数组进行整体输入输出。printf和scanf不支持对普通数组进行整体输入输出,必须以元素为单位进行操作,但对字符数组来说,可以通过“%s”进行整体输入或输出,这部分内容安排后面介绍。(3)数组比较。intx3=1,2,3;inty3=4,5,6;if(xy)/*错误*/(4)数组整体运算。intx5=5,6,7,8,9;inty5=2,3,4,5,6;x+=y;/*错误,其他运算与此同*/,5.2.11一维数组引用,一维数组数组地址的引用:printf(“请输入%d个数:n”,N);for(i=0;iN;i+)scanf(“%d”,a+i);for(i=0;iN-1;i+)min=i;for(j=i+1;jN;j+)if(ajamin)min=j;if(min!=i)temp=ai;ai=amin;amin=temp;for(i=0;iN;i+)printf(“%dn”,ai);,for(i=0;iN-1;i+)min=i;for(j=i+1;jN;j+)if(ajamin)min=j;if(min!=i)temp=ai;ai=amin;amin=temp;,5.2.17一维数组案例,用冒泡法对数组中的数进行排序。(按由小到大升序)思路:从一个数列的首部到尾部(也可以从尾部到首部),依次比较相邻的两个数据,将较小的放在前面。,5.2.18一维数组案例,此算法流程图如右:,5.2.19一维数组案例,#defineN10voidmain()intaN,i,j,temp;printf(“请输入%d个数:n”,N);for(i=0;iaj+1)temp=aj;aj=aj+1;aj+1=temp;for(i=0;iN;i+)printf(“%dn”,ai);,for(i=0;iaj+1)temp=aj;aj=aj+1;aj+1=temp;,5.2.20二维数组,一维数组常称为向量,本节介绍二维数组,所谓二维数组,最简单的理解是“有两个下标”,如果把一维数组理解为一行数据,那么,二维数组可形象地表示为行列结构,如所表示,左侧表示的是一个大小为M+1的一维数组,右侧表示的是一个大小为(M+1)*(N+1)的二维数组。,5.2.21二维数组,datatypearrayNamerowsizecolsize;,intnum42;,num,4X2=8,8*sizeof(int)字节,5.2.22二维数组,intbooks42=11,1294,22,450,33,4000,44,79;,intarr3=1,2,3,4,5,6;,intarr2=1,2,3,4,5,6;,5.2.23二维数组的初始化,二维数组同样可以在声明时利用初始化表进行元素的初始化,举例来说:intnum23=1,2,3,4,5,6;二维数组同样可对部分元素进行初始化,如:intnum23=1,2;说明:初始化表达式中内层花括号代表一行,这样,和一维数组中只能对前几个元素初始化不同,二维数组的初始化可跳过某些中间元素,给后面的元素赋值,在了解了二维数组内存分布后,理解可能更深刻。上述语句声明了一个2行3列的数组,只对每行的第1个元素进行了初始化,其他元素默认初始化为0,即上式等价于:intnum23=1,0,0,2,0,0;将一个二维数组中全部元素初始化为0的最简单的方式是:intnum23=0;当声明语句中提供有全部元素的初始值时,第1维的大小可以缺省,如:intsz4=1,2,3,4,5,6,7,8,9,10,11,12;,5.2.26二维数组引用,二维数组元素的引用:数组名行下标表达式列下标表达式如a00、aij注意:只能逐个引用各行各列的元素,不能整体引用;行下标和列下标均不做越界检查。,5.2.24二维数组引用,二维数组地址的引用:元素aij的地址是printf(“请输入n:);scanf(%d,5.2.27二维数组案例,输出一个3行4列矩阵的转置。,151262373480,a,123456781230,b,aij,bji,5.2.28二维数组案例,voidmain()inti,j,a34=1,2,3,4,5,6,7,8,1,2,3,0,b43;for(i=0;i3;i+)/*将a转置存入b*/for(j=0;j4;j+)bji=aij;printf(“转置后的矩阵为:n”);for(i=0;i4;i+)for(j=0;j3;j+)printf(“%8d”,bij);printf(“n”);,5.2.29二维数组案例,编写程序打印出杨辉三角形。,1111211331.,1000110012101331.,思路:,我们定义一个二维数组,所有元素先初始化为0;给数组的第1列和对角线元素赋值为1;其余元素aij=ai-1j-1+ai-1j;输出这个二维数组的下三角。,5.2.30二维数组案例,voidmain()inti,j,a1010=0;for(i=0;i10;i+)ai0=aii=1;for(i=1;i10;i+)for(j=1;ji;j+)aij=ai-1j-1+ai-1j;printf(“n杨辉三角为:n”);for(i=0;iA02-A03-A10-A11-A12-A13-A20-A21-A22-A23多维数组的存储方式与此类似,可以将下标看成是一个计数器,像计数的万位、千位、百位、十位和个位一样,右边的下标(靠后的下标)是低位,每一位都在上下界间变化,变化的范围是0到声明时指定的下标值减1,当某一低位计数器超出范围时(达到声明时指定的下标值),左边下标加1,同时该低位计数器及其右边的更低位计算器置0(回到下界)。这样,最左边一维下标变化是最慢的,最右一维下标变化最快。,5.2.34数组小结,数组的基本知识,首先介绍了数组的一般概念,而后从一维数组讲起,逐渐拓展到二维甚至三维等更高维的数组。在使用一个数组之前,必须对其进行声明,编译器根据声明语句为数组分配内存空间,这样,数组才有了实际意义,才能对数组元素进行读取操作。本结依托实例阐明了数组的声明方式,不论是数组还是普通变量,声明之处、使用之前对其进行初始化是十分重要的,否则,随机的内存值可能会给程序带来这样那样的问题。数组元素在内存中是连续排列的,对二维和更高维的方式,数组元素仍然是线性连续排列的,不同下标的关系类似于数字的不同位数,最左边的下标变换最慢,最右边的下标变化最快,理解数组的内存模型有利于写出高质量的代码。,1,2,3,输入10个数,存入一个数组,并输出从小到大,从大到小,实现循环一次初始化二维数组。,随机生成100个数,实现排序,5.2.35习题,5.3C语言最强悍的功能-指针,欢迎走进内存这片雷区,比尔盖茨曾经说过,640K内存对大多数应用来说应该足够了,看来天才也有说错话的时候,内存管理程序往往是最令程序员感到头大的地方,也是程序bug集中营,因此,掌握内存的基本知识是十分必要的,即将介绍内存的使用以及C语言的难点所在指针。,5.3.1计算机中的内存,熟悉计算机的大家知道,内存是平时接触较多的一个概念,问一个问题:内存是什么?从硬件形态上说,内存就是一条形物理设备,从功能上讲,内存是一个数据仓库,程序内在执行前都要被装载到内存中,才能被中央处理器执行。举Windows系统为例,执行安装在硬盘上的某个程序,实际上是将该程序的指令和数据导入内存,供中央处理器执行的过程。内存是由按顺序编号的一系列存储单元组成的,在内存中,每个存储单元都由唯一的地址,通过地址可以方便地在内存单元中存储信息。内存中的数据要靠供电来维持,当计算机关机或意外断电时,其中的所有数据就永久地消失了。,5.3.2内存地址,可以将内存看成一个个连续小格子的集合,为了正确地访问这些小格子,必须给这些小格子编号,正如平时我们讲某栋房屋在A小区X楼Y单元Z房间一样,这个A、X、Y和Z等实际上对该房间的编号,有了这个编号,或者更通俗地说是“地址”,我们就能从一个城市的万千栋长的几乎一样的房子中找到该房间。内存地址的引入是同样的道理,为了正确访问每个内存单元,对其进行编址,以32位计算机为例,其地址空间为32位,采用32位地址编码,诸如0X87654321的形式。内存地址是连续的,相邻内存单元间的地址差1,可以把内存看成一个平坦连续的一维空间。,5.3.3内存中保存的内容,内存中保存的是数据,这几乎是句废话,在计算机中,一切信息都是以二进制数据的形式体现的,每个内存单元的容量是1B,即8bit(8个0、1二进制位)。中央处理器,即CPU,进行的处理离不开内存,使用过windows系统的大家都知道,双击某个可执行程序,CPU会执行它,这实际上是复杂的内存载入过程:(1)程序要进行的操作对应的代码被装载到代码区。(2)全局和静态数据等装载到数据区(3)开辟堆栈,供临变量等使用可见,内存中的数据是多种多样的,既可以是操作,也可以是数据,都被存储在一个个的内存小格子中,每个小格子存储8个二进制位。,5.3.4内存与指针,程序中:inti;floatk;,内存中每个字节有一个编号-地址,i,k,编译或函数调用时为其分配内存单元,变量是对程序中数据存储空间的抽象,5.3.5指针与指针变量,指针:一个变量的地址指针变量:专门存放变量地址的变量叫,2000,指针,指针变量,变量的内容,变量的地址,5.3.6指针详解,内存,10,intx,ED53,地址,变量,数据,ED53,intptr_x,指针,指针ptr_x指向变量x,5.3.7地址就是指针,对么,在进一步说明指针概念前,本节将使大家对指针有个感性的认识,所谓指针,指的是“储存的内容是地址的量”,两个要点:一、指针是个量,对应着一块内存区域,二,指针存储的信息是某个内存单元的地址。指针的示意如所示。,5.3.8指针变量的声明,指针可以视为一个普通变量,通常所说的定义一个指针实际上是声明一个指针变量的过程,编译器根据指针变量声明语句,为指针变量开辟内存空间,使其有实际意义,这样,指针变量才可用。在声明一个指针变量时,需要向编译器提供以下信息:指针的类型,原则上指针类型应与其指向的数据类型一致,但也有例外,稍后会讲到。指针变量名。举例来说,下述语句用以声明一个指向int型数据的指针pInt:int*pInt;不难看出,要声明一个指向某种类型的指针变量,其基本形式为:类型*指针变量名;要在一行语句中同时声明两个指针变量,后面的指针变量前同样要加星号,如:int*p1=null,*p2=null;,5.3.9指针变量的声明,数据类型*指针名;,int*ptrnum;,char*ptralpha;float*rate_ptr;double*p,*q;,值为NULL的指针称为空指针,这意味着,指针并不指向任何地址。在头文件stdio.h中,NULL定义为常量。,ptrnum=NULL;,5.3.10指针变量的初始化,在声明一个指针后,编译器并不会自动完成其初始化,此时,指针的值是不确定的,也就是说,该指针指向那块内存单元是完全随机的,因此,指针变量的初始化十分重要,直接使用未加初始化的指针变量可能会给程序带来各种内存错误,因为完全不知道哪块内存会被修改掉。如果在指针变量声明之初确实不知道该将此指针指向何处,最简单的方式是将其置“0”,C语言中提供了关键字NULL,如下:int*pInt=NULL;这样,指针pInt便不会在内存中乱指一气。如果要让指针变量确切地指向某个变量,需要使用int*p;*p=i;printf(“%d”,*p);,危险!,例main()inti=10,k;int*p;p=,指针变量必须先赋值,再使用,5.3.12取地址操作符ptrnum=,取地址符,指针,FF7C,ptrnum=,间接运算符,15,*,5.3.14直接访问与间接访问,直接访问:按变量地址存取变量值间接访问:通过存放变量地址的变量去访问变量,例i=3;-直接访问,3,例*i_pointer=20;-间接访问,20,5.3.15直接访问与间接访问,例k=i;-直接访问k=*i_pointer;-间接访问,10,例k=i;k=*i_pointer;,5.3.16空指针,零指针零指针:(空指针)定义:指针变量值为零表示:int*p=0;,p指向地址为0的单元,系统保证该单元不作它用表示指针变量值没有意义,#defineNULL0int*p=NULL:,p=NULL与未对p赋值不同用途:避免指针变量的非法引用在程序中常作为状态比较,例int*p;.while(p!=NULL).,5.3.17指针代码实践,例指针的概念,main()inta;int*pa=,运行结果:a:10*pa:10scanf(%d,%d,运行结果:a=5,b=9max=9,min=5,5,2006,9,2008,2006,2008,2006,5.3.19指针代码实践,指针变量作为函数参数地址传递特点:共享内存,“双向”传递,swap(intx,inty)inttemp;temp=x;x=y;y=temp;main()inta,b;scanf(%d,%d,例将数从大到小输出,5,9,5,5,9,COPY,5.3.20指针代码实践,swap(int*p1,int*p2)intp;p=*p1;*p1=*p2;*p2=p;main()inta,b;int*pointer_1,*pointer_2;scanf(%d,%d,5,9,2000,2002,5,9,COPY,5,例将数从大到小输出,5.3.21指针代码实践,swap(int*p1,int*p2)int*p;*p=*p1;*p1=*p2;*p2=*p;main()inta,b;int*pointer_1,*pointer_2;scanf(%d,%d,运行结果:9,9,编译警告!结果不对!,intx;int*p=,例将数从大到小输出,5,9,2000,2002,9,9,COPY,假设2000,指针变量在使用前必须赋值!,5.3.22指针代码实践,swap(intx,inty)intt;t=x;x=y;y=t;main()inta,b;int*pointer_1,*pointer_2;scanf(%d,%d,运行结果:5,9,例将数从大到小输出,值传递,5,9,2000,2002,COPY,5,5,9,5.3.23指针代码实践,运行结果:5,9,例将数从大到小输出,swap(int*p1,int*p2)int*p;p=p1;p1=p2;p2=p;main()inta,b;int*pointer_1,*pointer_2;scanf(%d,%d,5,9,2000,2002,COPY,2000,地址传递,2000,2002,5.3.24指针变量占据的内存空间,指针变量声明后,编译器为其开辟一定的内存空间,即指针变量占据一定的内存空间,在中很好地体现了这一点,而且,不论是何种类型的指针,都占据4个内存字节(这是由32位地址数据决定的)。如果是64位操作系统,指针就是8个字节。,5.3.25指向指针的指针,指针变量也是变量,占据一定的内存空间,有地址,因此可以用一个指针指向它,这称为指向指针的指针,或二级指针,如所示。,5.3.26指针的类型和指针所指向的类型,原则上说,指针类型和指针所指向的类型应当是相同的,但也有例外,讨论之前,区分下两个概念,所谓指针类型,指的是声明指针变量时位于变量名前的“类型*”,而所谓指针所指向的类型,指的是为指针初始化或赋值的变量类型。理解两个类型的不同是掌握C语言指针关键所在,本节从以下2个方面讲述,让大家体会两者的不同:指针的类型和指针所指向的类型相同时,指针的赋值。指针的类型和指针所指向的类型不同时,指针的赋值。所谓指针赋值赋值包括两种情况,用代码表示会更直观一点,假定p1和p2是指针,而num是变量:p1=p2;/*指针间相互赋值*/或p1=/*取变量地址给指针赋值,包括初始化*/,5.3.27同类型指针的赋值,这是最常见的一种情况,如所示,pN1和pN2是两个相同类型的指针,执行“pN2=pN1;”这样一个赋值操作后,pN1和pN2指向同样的地址,也就是说,两个指针指向同一个内存单元,对*pN2的任何改动都会影响*pN1的值,反之亦然。,5.3.28指针的类型和指针所指向的类型不同,所谓“指针的类型和指针所指向的类型不同”,是指如下情况:(1)指向内存字节数大于指针类型占据的字节数doubleDnum;int*pI=/*p1为doulbe型指针,而p2为short型指针*/,5.3.29指针变量的值,“指针变量的值”是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32位程序里,所有类型的指针的值都是一个32位整数,因为32位程序里内存地址长度都为32位。“指针所指向的内存区“就是从指针的值所代表的那个内存地址开始,长度为sizeof(指针所指向的类型)的一片内存区。一个指针的值是A,即是说该指针指向了以A为首地址的一片内存区域;反之,说一个指针指向了某内存区域,即是说该指针的值是这块内存区域的首地址。,5.3.30指针的运算,作为一种特殊的变量,指针可以进行一些运算,但并非所有的运算都是合法的,指针的运算主要局限在加减算术和其他一些为数不多的特殊运算。,指针的运算指针变量的赋值运算p=(指针变量p2值p1)不能把一个整数p,也不能把p的值整型变量,如inti,*p;p=1000;()i=p;(),5.3.31指针的赋值运算,intx,*ptr_x,*ptr_y;ptr_x=,FF7C,FF7C,inta5,*pa;pa=a;,a0,a1,a2,a3,a4,23,43,11,50,46,FE60,FE64,FE68,FE6C,FE70,FE60,5.3.32指针的算术运算,int*ptrnum,arr_num8;ptrnum=,使用递增/递减运算符(+和-)将指针递增或递减,10,23,15,60,41,49,13,39,一个类型为T的指针的移动,以sizeof(T)为移动单位。,5.3.33指针运算,105,将指针加上或者减去某个整数值,ptrnum=,ptrnum=,41,60,5.3.34指针比较,比较两个指针,#includevoidmain()int*ptrnum1,*ptrnum2;intvalue=1;ptrnum1=,5.3.35指针的大小比较,对两个毫无关联的指针比较大小是没有意义的,因为指针只代表了“位置”这么一个信息,但是,如果两个指针所指向的元素位于同一个数组(或同一块动态申请的内存中),指针的大小比较反映了元素在数组中的先后关系。,假设ptr_a和ptr_b分别指向a和b,5.3.36指针运算,若有p=a(p指向数组a),则:p+(或p+=1),表示p指向下一元素。*p+与*(p+)等价。同样优先级,结合方向为自右向左。*(p+)与*(+p)。前者是先取*p的值,后使p值加1,相当于ai+;后者是先使p加1,再取*p,相当于a+i。(

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论