1、第5章 数组与指针 第1页,共63页。本章学习要点 1.掌握一维数组的定义、赋初值以及简单应用;2.掌握一维字符数组和字符串之间的关系,了解字符串的常用操作;3.掌握二维数组的定义、赋初值,了解其应用;4.掌握指针的定义和运算;5.掌握指针与一维数组、字符串的关系;6.掌握动态内存管理的方法;7.掌握别名引用和指针引用。第2页,共63页。第5章 数组与指针 5.1 一维数组 5.2 二维数组 5.3 指针 5.4 动态内存管理 5.5 引用第3页,共63页。5.1 一维数组 5.1.1 一维数组的定义和初始化1.定义 什么是数组?数组是按相邻内存单元存放的单种数据类型的一组元素。换句话说:数组
2、是一组元素的集合,这些元素具有相同的数据类型,并且这些元素在计算机内存中是被存放在相邻的内存单元中的,这些元素不需要使用不同的变量名来定义,它们共享同一个名称,并且通过它们在数组中的位置加以区分。我们把这个共享的名称叫做数组名,而把每个元素在数组中的位置用下标进行表示,下标一般从 0开始。第4页,共63页。在使用数组前必须进行声明,定义一维数组的格式如下:;例如:要定义一个整型数组用来存放全班30个学生的成绩。int cj30;此数组包含30 个元素,这30个元素分别为:cj0、cj1、cj29,数据类型都为整型。第5页,共63页。2.初始化 当我们声明了一个数组之后,相当于在内存中开辟了一串
3、连续的内存单元,接下来就要往这些内存单元中存储数据,我们把这一过程称之为赋值。如果是第一次进行赋值,则称之为初始化。给数组赋值有下面几种 方法:(1)可以给每个数组元素单独赋值:cj0=50;/可以直接赋以一具体的数据cj1=cj0;/可以把其它数组元素的值赋值给该数组元素cj2=x;/可以把其它变量的值赋值给该数组元素cj3=cj0+cj2;/可以把表达式的值赋值给该数组元素 第6页,共63页。(2)可以对整个数组赋值如:float money 3=12.5,34.6,5.8;或者float money 3;money 3=12.5,34.6,5.8;注意:这种赋值方法在使用时必须确保中给出
4、的数据个数必须要小于或者等于数组的实际大小,否则将会出现错误。第7页,共63页。在初始化时需要注意:(1)对数组赋值时,不能用一个数组对另外一个数组赋值。如:int merry3=mike3;(2)在声明数组的时候需要说明数组的大小,除非你在同一个语句中对它进行初始化。如:int merry=1,2,3;/说明该数组有三个元素这种情况是错误的:int merry;第8页,共63页。5.1.2 一维数组的应用【例5.1】通过计算机接收某班级30个同学的语文成绩,并且按照从高到低输出。#include class students float cj30;/定义一个cj数组,用于存储成绩 publi
5、c:void sortData()/接收数据并且排序for(int m=0;m30;m+)/接收30 个成绩coutcjm;第9页,共63页。float temp;/排序for(int i=0;i29;i+)for(int j=i+1;j30;j+)if(cjicjj)temp=cji;cji=cjj;cjj=temp;第10页,共63页。void display()for(int j=0;j 30;+j)cout cjj endl;int main()students C1;C1.sortData();C1.display();第11页,共63页。5.1.3一维字符数组与字符串1.一维字符数
6、组 如果数组存放的数据属于文本,我们就将这样的数组称为字符数组。(1)声明 一维字符数组的声明与一维整型数组的声明类 似,只是将表示类型的关键字换成char,格式如下 char 数组名数组大小;例如:char array10;代表在内存中申请10个连续的存储单元,这些存储单元用来存放字符。第12页,共63页。(2)赋值如:定义一个字符数组,需要存储的数据为:abcdchar name4;name0=a;name1=b;name2=c;name3=d;也可以这样写:char name4=a,b,c,d;第13页,共63页。2.字符串 字符串是一个以NULL(“0”)结尾的字符数组,换句话讲,如果
7、数组以NULL结尾,那么该数组就称为字符串,简称为串。在实际显示中,NULL是不可显示字符,所以不在屏幕上显示,它只表示串的结束。字符串的声明同字符数组,只是在赋值上有所区别。如:存储字符串mike char name5=”mike”;/字符串mike实际长度为5 或 char name=”mike”;/计算机根据实际值计算出数组长度 或 char name5;/先定义数组,在逐个赋值 name0=m;name1=i;name2=k;name3=e;name4=0;第14页,共63页。注意:双引号表示所括出来的数据是字符串,如果是单个的字符,应该用单引号,因此在上例中逐个赋值时为了表示所存储的
8、数据是字符串必须在最后存储一个0,代表字符串的结束。如果最后没有name4=0;该语句只能说明name数组是一个字符数组,它的长度为5,已经存储了4个字符,最后一个元素还没有赋值。例如:要记录学生的姓名,该如何定义数组?分析:由于学生的姓名是属于字符串,所以要定义一个字符串数组,如果每个学生的姓名长度在20个字符以内,则可以定义该数组的长度为21。char name21;第15页,共63页。5.1.4字符串的主要操作1.字符串的输入和输出 字符串的输入和输出与一般变量的输入与输出类似,用cin和cout实现。【例5.2】某网站需要记录来访成员的信息并显示它们,需要记录的是姓名、城市、电话号码。
9、#includeclass customer char name30;char city30;char phonenumber15;第16页,共63页。public:void acceptdata()coutname;coutcity;coutphonenumber;/只需要直接给出串名 void displaydata()cout”your name is:”nameendl;cout”your city is:”cityendl;cout”your telephonenumber is:”phonenumberendl;第17页,共63页。int main()customer s;cout
10、”please enter these information:”endl;s.accpetdata();s.displaydata();return 0;第18页,共63页。2.字符串的常用函数(1)strlen()求字符串的长度该函数将返回字符串中实际存储的字符个数,0除外,假 设 一 字 符 串 为“m i k e r”,串名为n a m e,则 语 句 coutstrlen(name)endl;将得到结果5。(2)strcpy()复制字符串要将一个字符串复制给另外一个字符串,不能采用以下这种方法:char amounts6=”hello”;char customer6;customer
11、=amounts;/非法操作 第19页,共63页。我们可以采用逐个赋值的方法:char amounts6=”hello”;char customer6;customer0=h;customer1=e;customer2=l;customer3=l;customer4=e;customer5=0;也可以用函数strcpy()实现:char amounts6=”hello”;char customer6;strcpy(customer,amounts);/*可以将amounts中的数据复制给 customer*/第20页,共63页。(3)strcat()字符串的连接该函数是将一个字符串连接到另一个
12、字符串的后面,得到一个新的字符串。如要将字符串customer连接到字符串amounts的后面,可以采用下列语句:char amounts12=”hello”;char customer6=”world”;strcat(amounts,customer);/*amounts字符串为“helloworld”,customer的值不变*/注意:要保证前一个字符串的空间足够大,否则将得不到正确结果。(4)strcmp()比较字符串在实际操作中,我们经常需要比较两个字符串,比如要将几个学生的姓名按照升序排列,这时不能用关系运算符来比较。我们用函数strcmp()第21页,共63页。【例5.3】会员购物
13、享受8折优惠,否则全价购买#include#includeclass cust char shenfen8;float money;public:void just()cout shenfen;coutmoney;第22页,共63页。if(strcmp(shenfen,huiyuan)=0)coutyour money is:money*0.8;else coutyour money is:money;int main()cust lihua;lihua.just();return 0;第23页,共63页。5.2 二维数组 5.2.1 二维数组的定义和初始化1.定义 一维数组在空间上,我们可以
14、将它看作是一行或者一列,是一维的、线性的;而二维数组是包含多行多列的一个矩阵,是二维的。2.声明 声明一个二维数组必须指出该数组包含的行数和列数。格式如下:数组名行数列数;第24页,共63页。假如定义一个包含2行3列的二维整型数组:int number23;该数组第一维大小是2,第二维大小是3,表示该数组声明的内存空间是一个2行3列的矩阵,这个数组包含的总元素个数是23=6,如图5-1表示:图5-1 2行3列的二维数组由于数组的下标是从0开始,所以该矩阵第一行为第0行,第一列为0列,number00表示的是第0行第0列的这个元素。number00 number01 number02 numbe
15、r10 number11 number12 第25页,共63页。3.赋值 我们可以在声明二维数组的同时给予初始化,当然也可以在以后进行初始化,但有一点需要注意,那就是二维数组同一维数组一样,在声明的同时需要说明大小,除非你在同一个语句中对它进行初始化,这时也须指定列数。由于二维数组涉及多行多列,因此在对其进行赋值时和一维数组有所不同。第26页,共63页。【例5.4】定义一个3行2列的整型数组,并进行赋值 int num32=15,32,10,21,90,7;在这个例子中,中的数据位于同一行中,由于该数组有3行,所以每行数据都被括起来,而所有的行都被包括在最外层的中。也可以写成:int num
16、2=15,32,10,21,90,7;这时省略了行数,但列数不能省略。下面这种写法就是错的:int num =15,32,10,21,90,7;/错误第27页,共63页。5.2.2 二维数组的应用【例5.5】通过计算机接收某班级10个同学的姓名,并按照升序排列#include#includeclass studentchar name1020;/定义一个二维数组,每行可存储20个字符,共10行void accept()int i;for(i=0;i10;i+)cout”please enter the students name:”namei;/只需给出需要存储的行数 第28页,共63页。vo
17、id px()/排序char stmp20;for(int i=0;i10;i+)for(int j=i;j0)strcpy(stmp,namei);strcpy(namei,namej);strcpy(namej,stmp);第29页,共63页。void display()for(int i=0;i10;i+)coutnameiendl;int main()student s;s.accept();s.px();s.display();return 0;第30页,共63页。5.3 指针 5.3.1指针的定义与初始化 1.定义 什么是指针?举例来讲,如果将计算机的各个内存单元比作是一个一个的小
18、抽屉的话,那么指针就是开启这些小抽屉的钥匙,换句话讲,指针就是一个指示器,它告诉程序可以在在哪块内存中找到数据。实际上,指针也是一个变量,指针中存放的是所指向的那块内存单元的地址。指针的定义方法如下:指针类型 *指针名;第31页,共63页。如:int *pname;这个声明语句起到的作用是:定义一个指针,该指针的名字为pname,它指向一个存放整型数据的存储地址。需要注意的是*并不是指针名的一部分,它的作用在于说明所定义的是一个指针变量,与我们前面所讲的基本数据类型变量的定义相区别。又如:char *psize;float *ptr;分别表示定义一个字符型指针变量和单精度指针变量。第32页,共
19、63页。2.初始化 对指针进行初始化其实就是将某块内存单元的地址赋值给它,但是怎么能够知道内存单元的地址呢?&这个符号能够帮助我们取得变量的地址。如:int tr;int*ptr=&tr;也可以这样写:int tr;*ptr;ptr=&tr;该语句定义了一个指针ptr,它指向一个整型变量,该整型变量为tr。在指针变量ptr中存放的是变量tr的地址。而*ptr指向的就是变量tr中的内容。下图5-2清晰的表明了指针的作用:100 变量 tr(存储的数据为 100)tr 的地址 Ptr 第33页,共63页。【例5.6】下列程序输出结果是什么?#include void main()int nNumb
20、er;int *p;nNumber=20;p=&nNumber;coutnNumber=nNumberendl;coutnNumber=*pendl;coutnNumber address=pendl;*p=30;coutnNumber=nNumberendl;第34页,共63页。程序运行结果为:nNumber=20nNumber=20nNumber address=0 x8fd5fff4nNumber=30 第35页,共63页。5.3.2指针的类型 指针是一个变量,因此指针也有相关的类型。但是和普通变量不同的是,不同数据类型的指针之间的区别不是在指针的表示上,也不在指针所持有的值上,指针的类
21、型指的是指针所指向的数据的类型,所以指针的类型必须和所指向的变量的类型相匹配。第36页,共63页。5.3.3指针运算指针也可以进行加减运算,但是和普通的整数加减运算并不相同。它允许以下这些运算:1.赋值运算 我们可以将一个变量的地址赋值给指针,如:int tr1;int *ptr=&tr1;/注意指针的类型在此处应该为整型 指针之间也可以进行相互赋值,如:int tr1;int *ptr1,*ptr2;ptr1=&tr1;ptr2=ptr1;第37页,共63页。2.算术运算 由于指针存储的是内存地址,所以指针的算术运算针对的都是整数。最常见的算术运算是加减运算,可以对指针加1或者减1。由于指针
22、是一个指示器,所以指针的加减运算并不仅仅是整数的加减,而在于指针所存储的内存地址变动后,指针所指向的内存单元也产生变化。如:int tr1;int *ptr=&tr1;ptr+;第38页,共63页。ptr tr1 tr1 tr2 ptr tr2 tr3 tr3 自增前 自增后 图5-3 程序运行结束后,指针变量ptr指向的并不是变量tr1,而是在tr1之后、与之相邻的另外一个内存单元。如图5-3所示:3.关系运算 指针之间可以进行比较,如果两个指针比较结果相同,说明它们指向的是同一个变量。第39页,共63页。5.3.4指针和一维数组的关系 指针经常与一维数组配合使用,这样能够非常方便的对数组进
23、行操作。int num3=10,20,30;int *p;p=&num0;/*通过把第一个数组元素的地址赋值 给指针变量将指针指向数组的开头*/如图5-4所示:p 10 num0 20 num1 30 num2 图5-4 第40页,共63页。执行以下程序段:int num3=10,20,30;int *p;p=&num0;p+;(*p)+;cout*p;结果如图5-5 10 num0 p 20 num1 30 num2 图5-5 第41页,共63页。p+的作用是使指针指向下一个元素,(*p)+的作用是使指针所指向的那个元素自增1。如果将(*p)+的括号去掉,程序的最后输出结果将会怎么样呢?in
24、t num3=10,20,30;int *p;p=&num0;p+;*p+;cout*p;结果如图5-6所示:10 num0 20 num1 p 30 num2 图5-6 第42页,共63页。【例5.7】用户输入10个数据,按照从小到大的顺序输出#include void main()int num10,temp;int*p;p=&num0;cout请输入10个数据:;for(int i=0;i*(p+i);coutendl;for(i=0;i10;i+)第43页,共63页。for(int j=i+1;j*(p+j)temp=*(p+i);/交换*(p+i)=*(p+j);*(p+j)=tem
25、p;cout排列后为:endl;for(i=0;i10;i+)cout*(p+i);第44页,共63页。5.3.5指针和字符串的关系 使用指针操作字符串非常的方便。有如下语句:char *p=”BeiJing”;指针p指向字符串BeiJing,那么如果要将该字符串输出怎么操作?语句 cout*pendl;能够实现吗?答案是否定的,该语句运行后得到结果是:B。语句char *p=”BeiJing”;的作用是使指针p指向字符串,而我们已经知道字符串其实是一个以0结尾的字符数组,所以指针p就是指向字符串的第一个元素,而通过cout*pendl;输出的就是字符串的第一个元素B。那要输出整个字符串怎么办
26、呢?第45页,共63页。我们可以使用语句:coutpendl;由于在程序中使用字符串的时候,字符串会占有一个连续的空间,所以只要给出指针名即可将该指针所指向的整个字符串输出。如果有人问如果要输出存储该字符串的内存的地址,该如何做呢?下列程序可以实现:char *p=”BeiJing”;void *n;n=p;coutn;我们可以定义一个通用指针(void指针),该指针可以指向任何类型的数据,通过n=p;可将字符串的地址赋值给void指针n.第46页,共63页。【例5.8】阅读下列程序#include int main()char *ptr=“welcome”;void *p;p=ptr;cou
27、t ptr p endl;ptr=ByeBye;p=ptr;cout ptr p endl;return 0;第47页,共63页。程序执行结果:welcome0 x8fd100aaByeBye0 x8fd100b2注意:如果使用字符串数组,不能出现下列情况char city20=”paries”;city=”BeiJing”;/错误 第48页,共63页。5.4 动态内存管理 我们都知道在使用数组之前必须首先定义数组,定义数组实际上就意味着在计算机内存中开辟一定的空间以便以后使用。但是实际上在很多时候一开始我们并不太清楚要定义的数组元素的个数,这使得我们在定义数组的时候产生了困惑,如果在定义数组
28、的时候预留最大的长度,这有可能会导致内存的浪费,该怎么办呢?动态管理内存能够很好地解决这一问题。所谓动态内存管理,其实就是在需要的时候申请内存,而在不需要的时候释放内存,这使得计算机的内存不会被浪费,提高使用效率。第49页,共63页。1.动态分配内存 使用new运算符能够在需要的时候申请内存。new运算符的语法是:=new;说明:(1)类型可以是整型、字符型或者是浮点型等。(2)左边的指针变量的类型应该与右边的变量类型匹配。例如:int *ptr1;ptr1=new int;/动态分配一块内存,并使指针ptr指向它float *ptr2;ptr2=new float10;/动态分配一个有10个
29、元素的数组,并使指针指向它 第50页,共63页。2.内存的释放 内存使用完毕,为了节约资源需要将其释放。可以使用delete运算符将其释放。delete运算符的语法是:delete ;例如:delete ptr1;/释放由ptr1指向的内存delete ptr2;/释放由ptr2指向的一个数组 这样一来,程序仅在执行到new运算符的时候分配内存,在执行到delete运算符的时候释放内存,大大的节约了计算机内存资源。第51页,共63页。3.动态内存管理的应用【例5.9】由用户决定需要输入的数据个数,在输入这些数据后输出这些数据的平均值。#include void main()int*p,n,su
30、m=0;float avg;cout您要输入几个数据?n;cout请输入n个数据:;p=new intn;/动态分配内存for(int i=0;i*(p+i);coutendl;第52页,共63页。for(i=0;in;i+,p+)sum=sum+*p;avg=sum/n;cout这些数据的平均值为:avgendl;delete p;/释放内存 第53页,共63页。5.5 引用 5.5.1引用的定义和初始化 1.定义 什么是引用?引用就是引入了对象的一个同义词,相当于给对象取个别名,通过使用别名来引用该对象进行操作。就像古人有字、号一样。使用别名的引用的定义方法是:&别名=变量名;例如:int
31、&anum=num;其中anum是num的别名,或者说是对num的引用,这两个名称指的是同一个变量。第54页,共63页。说明:(1)引用别名产生的操作和直接通过对象产生的操作是完全一样的。(2)一个变量可以有多个别名,每个别名产生的操作会影响其他所有的别名。(3)一个别名被定义后除非再次定义,否则将永远指向该变量。第55页,共63页。2.初始化 引用必须在定义的同时进行初始化。下面的操作是非法的:int&anum;int num=10;anum=num;/非法应该改为:int num=10;int&anum=num;第56页,共63页。【例5.10】阅读程序#includevoid swap(
32、int&,int&);/定义一个带参数的函数swap,使用别名引用class funint number1,number2;public:void get();void fun:get()cinnumber1;cinnumber2;swap(number1,number2);coutnumber1 number2endl;第57页,共63页。void swap(int&num1,int&num2)int temp;temp=num1;num1=num2;num2=temp;coutnum1 num2endl;int main()fun f;f.get();执行程序,输入:10 20输出结果为:
33、20 10 20 10第58页,共63页。5.5.2指针和引用 我们能够使用指针来实现引用,指针和引用在功能上比较相似,但是两者并不完全相同,引用是引用了某个变量的别名,这样一来,可以使不同的变量名共享同一个内存空间,或者可以说引用只是引用了某块内存的别名,引用本身不占内存;而指针是指向某块内存的一个指示器,它本身储存了内存的地址,有属于自己的内存空间。引用是直接访问某块内存,而指针的访问是通过地址指示的,属于间接访问。在操作中,引用在一开始就被定义成某块内存的别名,在此后除非再次定义,否则将永远指称该变量,而指针可以任意的改变所指向的内存。第59页,共63页。可以使用指针实现引用,指针引用有
34、什么样的优点呢?让我们来考虑下列问题:作为一个程序员,需要为超市编写一个库存管理系统,但是不同超市的库存商品类型是不一样的,有的多有的少,这怎么办?有人说按照最多的类型数目进行定义,但这将给某些小型超市带来资源的浪费,如果使用前面学过的动态分配内存行吗?可以,但是必须在指针引用的配合下才能够完成。所以我们说指针比引用更加灵活第60页,共63页。【例5.11】使用指针引用实现:输入两个数据,输出最大值#includevoid max(int*,int*);/定义一个带参数的函数swap,使用指针引用class funint number1,number2;public:void get();vo
35、id fun:get()cinnumber1;cinnumber2;max(&number1,&number2);第61页,共63页。void max(int*pnum1,int*pnum2)int temp;if(*pnum1*pnum2)temp=*pnum1;elsetemp=*pnum2;coutthe max is:temp;int main()fun f;f.get();return 0;第62页,共63页。【复习与提示】1.数组是按相邻内存单元存放的单种数据类型的一组元素,在使用数组之前必须要定义,数组中的元素通过下标区分,下标从开始。2.字符串是一个以”0”(NULL)结尾的字
36、符数组,它的实际长度是字符个数加1,NULL不显示在屏幕上,只是表示串的结束。3.数组在声明的同时必须说明其大小,除非你在同一个语句中对其进行赋值。4.指针是一个指示器,它通过存放内存的地址从而指向该内存空间,符号&可以取得内存空间的地址。符号*不属于指针的名称,只是用于说明该变量是一个指针。5.可以通过运算符new对内存进行动态管理,在需要的时候用该运算符激活内存,在不需要的时候是用delete释放内存。6.为了共享同一个内存空间,可以给一个变量取个别名,通过对该别名的引用来使用该内存空间。7.引用和指针不是同一个概念,指针本身就是一个变量,占一定的内存空间;而引用只是引用了某个变量的别名,本身不占内存空间。第63页,共63页。