Download presentation
Presentation is loading. Please wait.
1
C++语言程序设计教程 第5章 构造数据类型 第5章 构造数据类型
2
第5章 构造数据类型 学习目标 1. 深入理解数组的概念, 掌握数组应用的一般方法; 2. 深入理解指针的概念,掌握指针的使用;
C++语言程序设计教程 第5章 构造数据类型 第5章 构造数据类型 学习目标 1. 深入理解数组的概念, 掌握数组应用的一般方法; 2. 深入理解指针的概念,掌握指针的使用; 3. 注意指针与数组的区别,会使用多重指针以及指针与数组的多种混合体,会分配动态数组; 4. 理解引用的概念,掌握引用型函数参数的用法;
3
C++语言程序设计教程 第5章 构造数据类型 5.2 数组 数组: 数组是一组在内存中依次连续存放的、具有同一类型的数据变量所组成的集合体。其中的每个变量称为数组元素,它们属于同一种数据类型,数组元素用数组名与带方括号的数组下标一起标识。数组可以是一维的,也可以是多维的。 特点: 若干个同类型的数据元素,并且各个数据元素之间存在某种次序关系。这类数据有一个共同的特点:它们有若干个同类型的数据元素,并且各个数据元素之间存在某种次序关系。
4
5.2.1 一维数组定义与使用 一维数组定义的一般形式为: 说明: 注意: 数组属于构造数据类型,在使用之前必须先进行类型定义。
C++语言程序设计教程 第5章 构造数据类型 5.2.1 一维数组定义与使用 一维数组定义的一般形式为: 说明: 数组元素的类型可以是void型以外的任何一种基本数据类型,也可以是已经定义过的构造数据类型; 数组名是用户自定义的标识符,用来表示数组的名称,代表数组元素 在内存中的起始地址,是一个地址常量。 常量表达式必须是unsigned int类型的正整数。表示数组的大小或长 度,也就是数组所包含数据元素的个数。 [ ]是数组下标运算符,在数组定义时用来限定数组元素的个数。 数据类型 数组名[常量表达式]; 注意: 数组属于构造数据类型,在使用之前必须先进行类型定义。
5
C++语言程序设计教程 第5章 构造数据类型 5.2.1 一维数组定义与使用 数组定义之后,系统会将从内存中为其分配一块连续的存储空间,从第1个数据元素开始依次存放各个数组元素。例如,定义的数组a其内存排列(分配)示意图如图5-1所示。 例如: int a[5]; //定义了一个5个元素的整型数组a
6
5.2.1 一维数组定义与使用 a+n*sizeof(int) 数组元素的地址通过数组名来读取,其格式如下:
C++语言程序设计教程 第5章 构造数据类型 5.2.1 一维数组定义与使用 4. 数组的地址 数组元素的地址通过数组名来读取,其格式如下: 由于其地址不是实际的地址值,称这个地址表达式为符号地址表达式。例如: 一维数组元素a[5]的符号地址表达式为a+5。 若a是一个int型数组,数组的符号地址表达式a+n所表达的地址是第n+1个元素a[n]的地址,代表的实际地址值为: a+n*sizeof(int) 而不是:a+n。 数组名+整型表达式;
7
5.2.1 一维数组定义与使用 使用数组要注意: 在使用数组时最常犯的错误是下标越界,所谓的下标越界就是下标表达式的值
C++语言程序设计教程 第5章 构造数据类型 5.2.1 一维数组定义与使用 使用数组要注意: 在使用数组时最常犯的错误是下标越界,所谓的下标越界就是下标表达式的值 超出了数组定义时给定的元素个数,对于这种错误,编译器无法知道,往往在 运行时出错。因此在程序设计时应格外注意。 数组名是一个地址常量,不能作为左值(赋值的目标)。因此,不能将一个数组 整体拷贝给另外一个数组。 例如: int a[5],c[5],i; a=c; //错误! 正确的方法是将对应的元素进行拷贝,见下列程序段: for(i=0;i<5;i++) a[i]=c[i]; //将数组c中元素的值拷贝到数组c的对应元素中 在函数中,可以将一个一维数组作为函数的形式参数,用来接受一个一维数组传递过来的地址。
8
5.2.3 多维数组 C++语言程序设计教程 (1)三维以及高于三维的数组称为多维数组
第5章 构造数据类型 5.2.3 多维数组 (1)三维以及高于三维的数组称为多维数组 多维数组的定义与二维数组类似,可以一次定义,也可以逐步由低维数组定义。 例如: int b[2][3][4]; //定义了一个三维数组 也可用下列方式步定义: typedef int B1[3][4]; B1 b[2]; 多维数组在内存的排列方式同样是先排低维数组,由低向高依次排列。 如:b[2][3][4]的排列顺序为: b[0][0]:b[0][0][0],b[0][0][1],b[0][0][2],b[0][0][3] b[0] b[0][1]:b[0][1][0],b[0][1][1],b[0][1][2],b[0][1][3] b b[0][2]:b[0][2][0],b[0][2][1],b[0][2][2],b[0][2][3] b[1][0]:b[1][0][0],b[1][0][1],b[1][0][2],b[1][0][3] b[1] b[1][1]:b[1][1][0],b[1][1][1],b[1][1][2],b[1][1][3] b[1][2]:b[1][2][0],b[1][2][1],b[1][2][2],b[1][2][3]
9
C++语言程序设计教程 第5章 构造数据类型 5.2.3 多维数组 (2)多维数组的初始化与存取 多维数组的初始化形式与二维数组类似:有嵌套初值表、线性初值表两种形式。使用线性初值表初始化时,数组元素按内存排列顺序依次从初值表中取值。 对多维数组进行存取包括对数组元素的存取和对数组元素的地址的读取,当一个多维数组的下标数与维数相同时,为对数组元素的存取。 当下标个数小于维数时表示的是一个地址,表示地址时,下标也不能越界。 如:下列都是b[2][3][4]的地址表达式。 b[1]; //b[1][0][0]的地址; b[2]; //错误,下标越界; b[0]+1; //与b[0][1]相同,b[0][1][0]的地址。 b[1][2]; //b[1][2][0]的地址 b[1][2]+4; //b[1][2][4]的地址,但数组b中没有b[1][2][4]这个元素, 故指向了其它地方。
10
3)增加一行、一列,存储数组中每行元素之和,每列元素之和,数组中所有元素之和;
C++语言程序设计教程 第5章 构造数据类型 作业: 用C++编写一个程序,实现以下功能: 1)建立并初始化一个二维数组; 2)输出数组中每个元素; 3)增加一行、一列,存储数组中每行元素之和,每列元素之和,数组中所有元素之和; 4)输出数组中每行元素的起始地址、每列元素的起始地址和所有元素地址; 5)输出该数组对应矩阵的转置矩阵;
11
C++语言程序设计教程 第5章 构造数据类型 5.2.4 数组与函数 数组名是一个地址,不能当作一个左值,但是可以作为函数的形参,接受实参传送来的地址。当形参接受实参传送来的地址后,形参数组与实参共享内存中的一块空间。函数体通过形参对数组内容的改变会直接作用到实参上。数组名作为形参是数组应用的一个重要方面。 注意: (1)使用数组名传递地址时,虽然传递是地址,但形参与实参的地址 (数组)类型应一致。 (2)形式参数中数组元素个数没有给定,因此,在函数体中,对数组存取的下标可以为任意值而不会出现编译错误。但是,当这个下标超过了实在参数数组的个数范围时,存取的就不是实在参数数组中的内容了。
12
C++语言程序设计教程 第5章 构造数据类型 5.2.5 字符数组与字符串 存放字符型数据的数组称为字符数组。字符数组也分为一维数组和多维数组。前述的数组的定义及初始化同样适用于字符数组,除此以外,C++对字符数组的初始化还可以使用字符串形式。 1.用字符进行初始化 例如: char s1[ ]={'C','h','i','n','a'}; char s2[ ][4]={{'H','o','w'},{'a','r','e'},{'y','o','u'}}; 2.用字符串进行初始化 例如: char s3[ ]="China"; char s4[ ][4]={"how", "are", "you"};
13
5.3 指针 指针是C++语言最重要特性之一,也是C++的主要难点。
第5章 构造数据类型 5.3 指针 指针是C++语言最重要特性之一,也是C++的主要难点。 指针提供了一种较为直观的地址操作的手段,正确地使用指针,可以方便、灵活而有效地组织和表示复杂的数据。 指针在C++程序中扮演着非常重要的角色,从某种程度上而言,如果不能深刻的理解指针的概念,正确而有效的掌握指针,就不可能真正学好C++,但是指针也是我们最容易产生困惑并导致程序出错的原因之一。
14
C++语言程序设计教程 第5章 构造数据类型 5.3 指针 1. 地址与指针 地址: 当定义一个变量后,内存中将会划出一块由若干个存储单元组成的区域,用于保存该变量的数据。在内存里每个存储单元都有各自的编号,称为地址。 指针: 在C++中,提供了指针类型,它是一种用于存放内存单元地址的变量类型,地址就存储在这种指针类型的变量中。正因为指针变量存储的是地址,用它来指明内存单元,所以形象地称这种地址变量为指针。
15
5.3 指针 2. 指针变量的定义 数据类型是指针变量所指向对象的数据类型,它可以是基本数据类型, 也可以是构造数据类型以及void 类型。
C++语言程序设计教程 第5章 构造数据类型 5.3 指针 2. 指针变量的定义 每存储一个地址,就要定义一个指针变量。定义指针变量的格式如下: 数据类型是指针变量所指向对象的数据类型,它可以是基本数据类型, 也可以是构造数据类型以及void 类型。 变量名是用户自定义的标识符。 *表示声明的变量是一个指针变量,而不是普通变量。 例如: int *ip; //定义了一个int型的指针变量ip float *fp; //定义了一个float型指针变量fp typedef int A[10]; A *ap; // 定义了一个A类型的指针变量ap sizeof(ip)=sizeof(fp)=sizeof(ap)=4; 数据类型 * 变量名;
16
C++语言程序设计教程 第5章 构造数据类型 5.3 指针 3.指针的初始化与赋值 定义了一个指针,只是得到了一个用于存储地址的指针变量。若指针变量既没有初始化,也没有赋值,其地址值是一个随机的数。 (1)不要将一个非地址常量、变量以及无意义的实际地址赋给指针变量。如: int *p=100; //错误,100是一个int型常量,不是一个地址常量。 int *p=(char *)100; //危险!100是一个无意义的实际地址, 可能指向危险区域。 (2)可以使用一个已初始化的指针去给另一个指针赋值,但类型必须一致如果不一致,可 进行强制类型转换。 char *p=NULL; int *ip=(int *)p+100; //将char型指针强制转化成int型指针。 (3)对于基本数据类型的变量、数组元素我们可以使用取地址运算符&来获得它们的地址, 但是也只有类型一致才能赋值。 int a[10]; //定义int型数组 int *i_pointer=a; //定义并初始化int型指针 (4)有一种特殊的void类型指针,可以存储任何的类型地址;但将一个void类型的地址赋值 给非void类型的指针变量,要使用类型强制转换。 void v; //错误,不能定义void类型的变量 void *vp; //定义void类型的指针 数据类型 *指针变量名=初始地址表达式;
17
C++语言程序设计教程 第5章 构造数据类型 5.3 指针 4.指针运算 指针变量存放的是地址,因此指针的运算实际上就是地址的运算,但正是由于指针的这一特殊性,使指针所能进行的运算受到了一定的限制。指针通常进行下列几种运算:赋值运算、取值运算、算术运算、相减运算、比较运算。 (1)* 和&运算 *称为指针运算符。出现在数据定义语句中时,* 在数据类型与变量之间,是一个二元运算符,用来定义指针变量;出现指针变量表达式左边时,是一个一元运算符,表示访问指针所指对象的内容。 例如: int a[4]={1,2,3}; int *ip=&a[2]; cout<<*ip; // 输出ip指向单元的内容,内容为整型数3 *ip=100; // 将100赋给a[2];
18
5.3 指针 运行结果: (2)指针与整数的加减运算 typedef char A[10]; int *p1=b[1][0];
第5章 构造数据类型 5.3 指针 (2)指针与整数的加减运算 指针的加减运算与普遍变量的加减运算不同,由于指针存储的是变量的内存地址,指针加上或减去一个整数n,表示指针从当前位置向后或向前移动n个sizeof(数据类型)长度的存储单元。因此对于不同的数据类型,n的实际大小就不同。 例如程序段: int b[2][3][4]; typedef char A[10]; int *p1=b[1][0]; int *p2=(int *)b[1]; int *p3=(int *)(b+1); double *pd=(double *)p3; A *pa=(A *)p3; cout<<p1<<","<<p2<<","<<p3<<","<<pd<<","<<pa<<endl; cout<<p1+1<<","<<p2+1<<","<<p3+1<<","<<pd+1<<","<<pa+1<<endl; 运行结果: 0013FF80, 0013FF80, 0013FF80, 0013FF80, 0013FF80 0013FF84, 0013FF84, 0013FF84, 0013FF88, 0013FF8A
19
5.3 指针 (3)指针自增、自减运算 (4)两指针相减
C++语言程序设计教程 第5章 构造数据类型 5.3 指针 (3)指针自增、自减运算 指针的自增、自减运算是指针加减运算的特例。指针的自增或自减表示指针从当前位置向后或向前移动sizeof(数据类型)长度的存储单元。 例如:程序段: int *p, *q, a=5; p=&a; p++; //指针p后移4个字节 *p++; //先读取p指向的变量a的值5,然后使指针p后移4个字节 (*p)++; //读取p指向的变量a的值,然后使p指向的变量a自增1 *++p; //先使指针p后移4个字节,然后读取p指向的变量的值 ++*p; //将p指向的变量a自增1 *q++=*p++; //这是一种常用的表达式,依次执行:*q=*p, q++, p++ (4)两指针相减 当两个指针指向同一数组时,两个指针的相减才有意义。两个指针相减的结果为一整数,表示两个指针之间数组元素的个数。
20
C++语言程序设计教程 第5章 构造数据类型 5.3 指针 (5)两个指针的比较运算 两个指针的比较一般用于下列两种情况:一是比较两个指针所指向的对象在内存中的位置关系;二是判断指针是否为空指针。 5. void类型指针 指向void类型的指针是一种不确定类型的指针,它可以指向任何类型的变量。实际使用void型指针时,只有通过强制类型转换才能使void型指针得到具体变量的值。在没有转换前void型指针不能进行指针的算术运算。 例如: void *vp; //定义了一个void型指针vp int i=6, *ip; vp=&i; // vp指向整型变量i cout<<“i=”<<*vp<<endl; // 错误 cout<<"i="<<*(int *)p<<endl; ip=(int *)vp; //ip指向vp指向的变量i cout<<"i="<<*ip<<endl;
21
C++语言程序设计教程 第5章 构造数据类型 5.3.3 指针与数组 1 使用指针操作符*存取数组 指针的加减运算的特点使得指针操作符特别适合处理存储在一段连续内存空间中的同类型数据。这样,使用指针操作符来对数组及其元素进行操作就非常方便。 (1) 一维数组的指针操作 当定义数组一维数组T a[N] (T为类型),下式为存取数组元素a[i]的等效方式: *(a+i); 而a+i为a[i]的地址。 (2) 多维数组的指针操作 对于多维数组,使用指针同样可以灵活访问数组元素。若定义数组 T b[K][M][N], 下式为存取数组元素a[i][j][k]的等效方式: 例如: *(*(*(b+i)+j)+k); 其中:*(*(b+i)+j)+k为b[i][j][k]的地址,即&b[i][j][k]; *(*(b+i)+j)为b[i][j][0]的地址,即b[i][j]; *(b+i)为b[i][0][0]的地址,即b[i];
22
5.3.3 指针与数组 2 指针数组 指针数组是以指针变量为元素的数组,指针数组的每个元素都是同一类型的指针变量。 注意:
C++语言程序设计教程 第5章 构造数据类型 5.3.3 指针与数组 2 指针数组 指针数组是以指针变量为元素的数组,指针数组的每个元素都是同一类型的指针变量。 在指针数组的定义中有两个运算符:*和[ ],运算符[ ]的优先级高于*,所以*p[N]等价于*( p[N]),p [N]表示一个数组,而*表示数组元素为指针变量。 例如: int *p_a[5]; 定义了一个指针数组,其中有5个数组元素,每个数组元素都是一个int类型的指针。 注意: 对指针数组的存取同样可以使用指针方式、数组方式、以及指针与数组结合的方式。一维指针数组与二维指针数组对应,可采用存取二维数组的方式存取。 指针数组的每一个元素都是一个指针,因此必须先赋值,后引用。
23
5.3.3 指针与数组 3)数组指针 数组指针是指向数组的指针。
C++语言程序设计教程 第5章 构造数据类型 5.3.3 指针与数组 3)数组指针 数组指针是指向数组的指针。 虽然运算符[ ]的优先级高于*,但是,在数组指针的定义式中,(* 指针名)改变了这种优先级,它表明定义的首先是一个指针,然后才是什么类型的指针。 例如: int (* a_p) [5]; 等效于下列定义方式: ①typedef int I_A[5]; ②I_A *a_p;
24
C++语言程序设计教程 第5章 构造数据类型 5.3.4 多重指针 如果已经定义了一个指针类型,我们再定义一个指针,用于指向已经定义的指针变量,后面定义的指针变量就是一个指向指针的指针变量,简称指向指针的指针,这样的指针也称二重(级)指针。 三重及以上的指针统称为多重指针。 例如: int **pp; 定义了一个二级指针变量,等效于下列定义方式: typedef int * P; P *p; 二级指针常用来指向一个指针数组。 例①: int a[2][3]={1,2,3,4,5,6}; //声明并初始化二维数组 int *p_a[3]; //声明整型指针数组 p_a[0]=a[0]; //初始化指针数组元素 p_a[1]=a[1]; pp=p_a;
25
5.3.4 多重指针 注意:在使用二级指针时经常容易犯两类错误。 (1)类型不匹配
C++语言程序设计教程 第5章 构造数据类型 5.3.4 多重指针 注意:在使用二级指针时经常容易犯两类错误。 (1)类型不匹配 例如: pp=a; //错误,因为pp是一个int **型变量,a是一个int [2][3]型的地址; pp=&a; //错误,pp是一个int **型变量,&a是一个(*)int [2][3]型的地址; pp=&a[0];//错误, pp是一个int **型变量,&a[0]是一个(*)int [3]型的地址 (2)指针没有逐级初始化 例如: int i=3; int **p2; *p2=&i; **p2=5; cout<<**p2; 虽然上述程序段编译、连接均没有错误,但运行时出错。其原因在于int **p2;只是定义了一个指针变量,变量中的内容(地址)是一个无意义的地址,而*p2=&i是对无意义的内存单元赋值,这样是错误与危险的。正确的作法是从第一级指针开始,逐级初始化。
26
5.3.4 多重指针 3)逐级初始化多级指针 例②: int i=3; int **p2; int *p1;
C++语言程序设计教程 第5章 构造数据类型 5.3.4 多重指针 3)逐级初始化多级指针 例②: int i=3; int **p2; int *p1; *p1=&i; //初始化一级指针 p2=*p1; //初始化一级指针 **p2=5; //通过指针给变量i赋值 cout<<**p2; //结果为5 上述两个二级指针在内存中 的分布如右图所示。
27
5.3.4 多重指针 注意: 当一个二级指针指向一个指针数组后,对数组元素的存取可以使用指针方式、数组方式、混合方式: 例如:
C++语言程序设计教程 第5章 构造数据类型 5.3.4 多重指针 当一个二级指针指向一个指针数组后,对数组元素的存取可以使用指针方式、数组方式、混合方式: 例如: p[i][j]; //存取数组元素a[i][j]的数组方式; *(*(p+i)+j); //存取数组元素a[i][j]的指针方式; *(p[i]+j); //存取数组元素a[i][j]的混合方式; 一般而言,二重指针就足够使用了,三重指针使用场合就很少了,使用多重指针同样要: 注意: 不管是多少重指针,定义后,只建立了一个指针变量,分配一个指针变量的空间。 要初始化多重指针,要从第一层开始,逐步向高层进行。
28
C++语言程序设计教程 第5章 构造数据类型 5.3.5 动态内存分配 在C++中,动态内存分配技术可以保证程序在运行过程中根据实际需要申请适量的内存,使用结束后还可以释放。C++通过new运算和delete运算来实现动态内存分配。 1. new运算 new运算的作用是按指定类型和大小动态的分配内存。 基本语法形式为: 其中: 数据类型可以是基本数据类型,也可以是用户自定义的复杂数据类型。 new运算符在堆(内存)中创建一个由类型名指定类型的对象,如果创建成功, 返回对象的地址;否则返回空指针NULL。 初值列表给出被创建对象的初始值。 由于返回的是地址,所以要用事先定义一个类型相同的指针变量来存储这个 地址。 指针变量=new 类型名 (初值列表);
29
5.3.5 动态内存分配 1)new创建指定类型对象 例①: int *ip;
C++语言程序设计教程 第5章 构造数据类型 5.3.5 动态内存分配 1)new创建指定类型对象 例①: int *ip; ip=new int(5); //ip指向1个初值为5的int型对象 也可以使用一条语句定义: int *ip=new int(5); 说明: 首先定义了一个整型指针ip; 然后申请内存,创建一个int型数据对象,并将该数据对象初始化为5; 最后返回创建的数据对象的地址,存入ip。
30
5.3.5 动态内存分配 2)new创建动态数组时 其语法格式如下: 其中: 下标表达式与数组初始化时的常量表达式不同,可以是变量表达式。
C++语言程序设计教程 第5章 构造数据类型 5.3.5 动态内存分配 2)new创建动态数组时 使用new运算可创建一个数据对象,也可以创建同类型的多个对象----数组。由于数组大小可以动态给定,所创建的对象称为动态数组。new创建动态数组时,需要给出数组的结构说明。 其语法格式如下: 其中: 下标表达式与数组初始化时的常量表达式不同,可以是变量表达式。 用new申请失败时,返回NULL。申请一个动态数组,往往需要较大的空间,因此,在程序中需要对new的返回值进行判断,看是否申请成功。 例②: int *pa; pa=new int [5]; //ip指向5个未初始化的int型数据对象的首地址 指针变量=new 类型名 [下标表达式];
31
指针变量=new 类型名T [下标表达式1][ 下标表达式2][…]
C++语言程序设计教程 第5章 构造数据类型 5.3.5 动态内存分配 3)new创建多维数组 使用new也可创建多维数组,其语法形式如下: 其中: 当用new创建多维数组时,只有下标表达式1可以任意结果是正整数的表达式,而其它下标表达式必须是值为正整数的常量表达式。 如果内存申请成功,new运算返回一个指向新分配内存首地址的指针,它是一个T类型数组的指针,而不是T类型指针。数组元素的个数为除最左边一维(最高维)外各维下标表达式的乘积。 例③: int *pb; pb = new int[3][4][5]; //错误,new操作产生的是指向一个int[4][5]的二维int //型数组的指针 int (*pb)[4][5]; pb = new int[3][4][5]; //正确,针pb既可以作为指针使用,也可以像一个 //三维数组名一样使用 指针变量=new 类型名T [下标表达式1][ 下标表达式2][…]
32
5.3.4 多重指针 2、delete运算 当程序不再需要由new分配的内存空间时,可以用delete释放这些空间。 其语法格式为:
C++语言程序设计教程 第5章 构造数据类型 5.3.4 多重指针 2、delete运算 当程序不再需要由new分配的内存空间时,可以用delete释放这些空间。 其语法格式为: 如果删除的是动态数组,则语法格式为: 其中: 括号[ ]表示用delete释放为多个对象分配的地址,[ ]中不需要说明对象的个数。 不管建立的动态数组是多少维,释放的格式都是一样。 对于例①②③分配的空间,释放语句如例④: delete ip; delete [] pa; delete [] pb; delete 指针变量名; delete [ ] 指针变量名;
33
C++语言程序设计教程 第5章 构造数据类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 /********************************* * p5_7.cpp * * 动态二维矩阵 * **********************************/ #include<iostream> using namespace std; void main() { int m,n; int **dm; cout<<"input matrix size m,n:"; cin>>m>>n; dm=new int * [m]; //建立m个指针,存储m行 for(int i=0;i<m;i++) //为每行分配n个空间 if((dm[i]=new int [n])==NULL) exit(0); for (i=0;i<m;i++) //输入矩阵元素 for(int j=0;j<n;j++) cin>>dm[i][j]; }
34
input matrix size m,n:2 3↙ 1 2 3↙ 4 5 6↙ 1 2 3 4 5 6 22 23 24 25 26 27
C++语言程序设计教程 第5章 构造数据类型 22 23 24 25 26 27 28 29 30 31 for (i=0;i<m;i++) //输出输出矩阵元素 { for(int j=0;j<n;j++) cout<<dm[i][j]<<"\t"; cout<<endl; } for(i=0;i<m;i++) //释放m个指针指向的空间; delete [ ] dm[i]; delete [ ] dm; //释放m个指针 注意: 各用new运算符申请分配的内存空间,必须用delete释放; delete作用的指针必须是由new分配内存空间的首地址; 对于一个已分配内存的指针,只能用delete释放一次; new和delete运算实现了堆空间的动态分配,它在带来方便的同时也潜伏了可能 的隐患:使用new进行内存分配之后,忘记了使用delete运算进行内存回收,即 “内存泄露”,如果程序长时间运行,则有可能因内存耗尽而使系统崩溃,所 以对new和delete要养成配对使用的良好习惯。 运行结果: input matrix size m,n:2 3↙ 1 2 3↙ 4 5 6↙
35
C++语言程序设计教程 第5章 构造数据类型 5.3.6 指针与函数 1、指针作为函数参数 指针作为函数参数是一种地址传递方式。当需要在不同的函数之间传递大量数据时,程序执行时调用函数的开销就会比较大,这时,如果需要传递的数据是存放在一个连续的内存区域中,就可以采用指针作为函数参数,只传递数据的起始地址,而不必传递数据的值,这样就会减小开销,提高效率。 指针可以作为函数的形参,也可以作为函数的实参。如果以指针作为函数的形参,在调用时实参将值传递给形参,也就是使实参和形参指针变量指向同一内存地址,这样在子函数运行过程中,通过形参指针对数据值的改变也同样影响着实参所指向的数据值,从而实现参数双向传递的目的,即通过在被调用函数中直接处理主调函数中的数据而将函数的处理结果返回其调用者。 【例5-8】用传指针方式实现两个数据交换 分析:用传值的方式无法实现两个数据交换。用指针作为形参,从实参传入要交换数据的地址,在函数体内将指针指向的两个数据交换存储位置,这样通过“釜底抽薪”的方式实现了数据交换。为了实现不同类型的数据交换,形式参数采用void指针。
36
C++语言程序设计教程 第5章 构造数据类型 5.3.6 指针与函数 2 指针型函数 除void类型的函数之外,每个被调用函数在调用结束之后都要有返回值,指针也可以作为函数的返回值。当一个函数声明其返回值为指针类型时,这个函数就称为指针型函数。 指针型函数定义形式为: 其中, 数据类型表明函数返回指针的类型;*表示函数的返回值是指针型,可以使用多个*返回多重指针。 使用指针型函数的最主要目的就是要在函数调用结束时把大量的数据从被调用函数返回到主调函数中,而通常非指针型函数调用结束后,只能返回一个值。 在调用指针型函数时,需要一个同类型的指针变量接受返回的值。 数据类型 * 函数名(参数表);
37
C++语言程序设计教程 第5章 构造数据类型 5.3.6 指针与函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 /******************************** * p5_9.cpp * * 超长整数加法 * ********************************/ #include <iostream> using namespace std; char *ladd(char *s1, char *s2) { int n1,n2,n; char *res,c=0; n1=strlen(s1); //n1=数字串s1的长度 n2=strlen(s2); //n2=数字串s2的长度 n = n1>n2 ? n1 : n2; //数字串s1,s2最大长度 res=new char [n+2]; //申请存结果串的内存 for(int i=n+1;i>=0;i--) //将s1从低位开始搬到res, res[i] = i>n-n1 ? s1[i-n-1+n1] : '0';
38
C++语言程序设计教程 第5章 构造数据类型 5.3.6 指针与函数 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 for(i=n;i>=0;i--) { char tchar; tchar = i>n-n2 ? res[i]-'0'+s2[i-n+n2-1]-'0'+c : res[i]-'0'+c; //将数字符变成数 c = tchar>9 ? 1 : 0; //设进位 res[i] = c>0 ? tchar-10+'0' : tchar+'0'; //将数字变成数字字符 } return res; void main() { char num1[100],num2[100],*num; cin>>num1>>num2; num=ladd(num1,num2); cout<<num1<<"+"<<num2<<"="<<num<<endl; delete [ ] num; 运行结果: ↙ 99999↙ =
39
C++语言程序设计教程 第5章 构造数据类型 5.3.6 指针与函数 3 指向函数的指针 在程序运行时,不仅数据要占用内存空间,程序的代码也被调入内存并占据一定的内存空间。每一个函数都有函数名,实际上,这个函数名就是该函数的代码在内存的起始地址。 当调用一个函数时,编译系统就是根据函数名找到函数代码的首地址,从而执行这段代码。由此看来,函数的调用形式:函数名(参数表),其实质就是:函数代码首地址(参数表)。 函数指针: 就是指向某个函数的指针,它是专门用于存放该函数代码首地址的指针变量。一旦定义了某个函数指针,那么,它就与函数名有同样的作用,在程序中就可以象使用函数名一样,通过指向该函数的指针来调用该函数。
40
5.3.6 指针与函数 函数指针的定义语法形式如下: 其中:
C++语言程序设计教程 第5章 构造数据类型 5.3.6 指针与函数 函数指针的定义语法形式如下: 其中: 数据类型为函数指针所指函数的返回值类型;形参表则列出了该指针所指函数的形参类型和个数。 函数指针名与*外面的圆括号()是必须的,圆括号改变了默认的运算符的优先级,使得该指针变量被解释为指向函数的指针。如果去掉圆括号,将被解释为函数的返回值为指针。 数据类型 (*函数指针名)(形参表);
41
5.3.6 指针与函数 函数指针在使用之前也要进行赋值,使指针指向一个已经存在的函数代码的起始地址。语法形式为:
C++语言程序设计教程 第5章 构造数据类型 5.3.6 指针与函数 函数指针在使用之前也要进行赋值,使指针指向一个已经存在的函数代码的起始地址。语法形式为: 赋值符右边的函数名所指出的必须是一个已经声明过的,和函数指针具有相同返回类型和相同形参表的函数。在赋值之后,就可以通过函数指针名来直接引用该指针所指向的函数。即:该函数指针可以与函数名一样,出现在函数名能出现的任何地方。 调用函数指针指向的函数有如下两种格式: 例如: int add(int a,int b); //定义函数 int (*fptr)(int a,int b); //定义函数指针 fptr=add; // 函数指针赋值,或fptr=&add; 函数指针名=函数名; ① 函数指针名(实参表); ② (* 函数指针名) (实参表);
42
5.3.6 指针与函数 说明: 采用下列任何一种形式调用函数sum: add(1,2); //用函数名调用函数sum
C++语言程序设计教程 第5章 构造数据类型 5.3.6 指针与函数 采用下列任何一种形式调用函数sum: add(1,2); //用函数名调用函数sum (*fptr)(1,2); //用指向函数的指针调用函数sum fptr(1,2); //用指向函数的指针调用函数sum 【例5-10】用指向函数的指针实现各种算术运算的菜单程序。 分析:为了实现菜单功能,需要定义一个函数指针数组来存储各函数名(地址),调用时通过各数组元素指向的函数名来调用对应的函数。 说明: 虽然三种调用形式的结果完全相同,当用指向函数的指针调用函数add()时,习惯上使用 (*fptr)(1,2),因为这种形式能更直观地说明是用指向函数的指针来调用函数。 指向函数的指针常用来实现菜单程序,根据不同的选择执行菜单项中对应的功能,各功能由指向函数的指针实现。
43
C++语言程序设计教程 第5章 构造数据类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 /******************************* * p5_10.cpp * * 菜单程序 * ********************************/ #include <iostream> using namespace std; int add(int a,int b) { return a+b; } int sub(int a,int b) { return a-b; int mul(int a,int b) { return a*b; int divi(int a,int b) { if (b==0) return 0x7fffffff; else return a/b; int (*menu[ ])(int a,int b)={add,sub,mul,divi};
44
Select operator: 1: add 2: sub 3: multiply 运行结果: C++语言程序设计教程 第5章
构造数据类型 21 22 23 24 25 26 27 28 29 30 31 32 void main() { int num1,num2,choice; cout<<"Select operator:"<<endl; cout<<" : add"<<endl; cout<<" : sub"<<endl; cout<<" : multiply"<<endl; cout<<" : divide"<<endl; cin>>choice; cout<<"Input number(a,b): "; cin>>num1>>num2; cout<<"Result:"<<menu[choice-1](num1,num2)<<endl; } 运行结果: Select operator: 1: add 2: sub 3: multiply
45
C++语言程序设计教程 第5章 构造数据类型 5.3.7 指针常量与常指针 1. 指针常量 如果在定义指针变量时,指针变量前用const修饰,被定义的指针变量就变成了一个指针类型的常变量,指针类型的常变量简称为指针常量。 定义指针常量格式如下: 修饰符const与指针变量紧邻,说明指针变量不允许修改。既然指针变量的值不能修改,所以一定要在定义时给出初值。 例如: char * const p="ABCDE"; //定义了一个指针常量 p=NULL; //错误,指针常量被赋给一个地址常量 p="1234"; //错误,指针常量被赋给一个地址常量 p=(char *)q; //错误,指针常量被赋给一个地址变量的值 因为const修饰的是指针变量,而不是指针指向的值。所以指针指向的值可以更改: *p='1'; //正确,更改指针指向的值。 p[1]='1'; //正确,用数组访问方式更改指针指向的值。 数据类型* const 指针变量 = 变量名;
46
C++语言程序设计教程 第5章 构造数据类型 5.3.7 指针常量与常指针 2. 常指针 如果在定义指针变量时,数据类型前用const修饰,被定义的指针变量就是指向常量的指针变量,指向常量的指针变量简称为常指针。 定义常指针的格式如下 定义一个常指针后,指针指向的值就不能被更改,即不能通过指针变量直接更改指针指向的值。 const char * p="ABCDE"; //定义了一个常指针 *p='1'; //错误,试图更改指针指向的值。 为了防止通过一个非常指针修改常指针指向的值,将一个常指针赋给一个非常指针是错误的: char *q; q=p; //错误,将一个常指针赋给非常指针。 const 数据类型* 指针变量 = 变量名; 或 数据类型 const * 指针变量 = 变量名;
47
5.3.7 指针常量与常指针 注意:常数组的所有元素必须全部赋初值。
C++语言程序设计教程 第5章 构造数据类型 5.3.7 指针常量与常指针 可以将一个非常指针赋给一个常指针,这是因为const修饰的是指针指向的内容,而不是指针的值(地址值)。 p=NULL; //正确 p=q; //正确 const用在数组的类型前修饰数组元素,数组元素为常量的数组的称为常数组,常数组的元素不可改变,也不可将地址赋值给非常指针变量。 const int a[3]={1,2,3}; //定义常数组; a[0]=0; //错误,常数组的元素不可修改 int *p=a; //错误,常数组的地址不能赋给非常指针变量 注意:常数组的所有元素必须全部赋初值。 const int a[]={1,2,3}; //正确 const char a[]={'1','2','3'}; //正确 const int a[10]={1,2,3}; //错误,常数组元素没有全部赋初值
48
C++语言程序设计教程 第5章 构造数据类型 5.3.7 指针常量与常指针 3. 常指针常量 指针常量保护指针的值不被修改,常指针保护指针指向的值不被修改,为了将两者同时保护,可以定义常指针常量,常指针常量意为一个指向常量的指针,指针值本身也是一个常量。 常指针类型通常用作函数的形参,以防止在函数体内通过形参修改实参指向的值,以保护实参。 常指针常量定义格式如下: 其中: 左边的const与数据类型相结合,表明数据的值是常量;右边的const用在变量前,表明变量的值是常量。 定义一个常指针常量后,修改指针的值与修改指针指向内容的值都是错误的: char *q; const char * const p="ABCDE"; //定义了一个常指针常量 q=p; //错误,将一个常指针赋给非常指针。 p=q; //错误,试图改变指针常量的值。 const 数据类型* const 指针变量 = 变量名; 或 数据类型 const * const 指针变量 = 变量名;
49
C++语言程序设计教程 第5章 构造数据类型 5.4 引用类型 引用: 引用是已存在变量的别名,对引用型变量的操作实际上就是对被引用变量的操作。当定义一个引用型变量时,需要用已存在的变量对其初始化。 定义一个引用型变量的语法格式为: 其中: 数据类型应与被引用变量的类型相同; &是引用运算符, 在这里是二元操作符; 变量名为已定义的变量; 例如: int x; int & refx=x;//refx是一个引用型变量,它被初始化为对整型变量x的引用 数据类型 & 引用变量名 = 变量名;
50
#include<iostream.h> void Swap(int& a, int& b); int main() {
第5章 构造数据类型 例 输入两个整数交换后输出 #include<iostream.h> void Swap(int& a, int& b); int main() { int x(5), y(10); cout<<"x="<<x<<" y="<<y<<endl; Swap(x,y); return 0; } 交换函数以引用为参数,达到了交换目的。
51
运行结果: x=5 y=10 x=10 y=5 void Swap(int& a, int& b) { int t; t=a; a=b;
C++语言程序设计教程 第5章 构造数据类型 void Swap(int& a, int& b) { int t; t=a; a=b; b=t; } 运行结果: x= y=10 x= y=5
52
t t y Swap(x,y); t=a; x a x y a b a b x y a=b b=t; b x y 5 x 的地址 5 10
C++语言程序设计教程 第5章 构造数据类型 Swap(x,y); t=a; x 5 t x 的地址 a x y 5 10 y 的地址 x 的地址 a b y 的地址 x 的地址 a b x 10 y a=b b=t; y 5 t y 的地址 b x y 10 5
53
C++语言程序设计教程 第5章 构造数据类型 5.4.1 引用类型变量的说明及使用 当定义一个引用变量后,系统并没有为它分配内存空间。refx与被引用变量x具有相同的地址,即refx与x使用的是同一内存空间。对引用变量值的修改就是对被引用变量的修改,反之亦然。 例如: x=3; cout<<refx; //结果为3 refx=5; cout<<x; //结果为5 引用变量的内存图如图5-6。
54
C++语言程序设计教程 第5章 构造数据类型 5.4.2 引用与函数 1. 引用作为函数的参数 当引用作为函数的形参,在进行函数调用时,进行实参与形参的结合,其结合过程相当于定义了一个形参对实参的引用。因此,在函数体内,对形参进行运算相当于对实参进行运算。 与指针相比,引用作为函数参数具有两个优点: ① 函数体的实现比指针简单。用指针作为形参,函数体内形参要带着*参加运算;而用引用作为形参,函数体内参加运算的为形参变量。 ② 调用函数语法简单。用指针作为形参,实参需要取变量的地址;而用引用作为形参,与简单传值调用一样,实参为变量。
55
C++语言程序设计教程 第5章 构造数据类型 5.4.2 引用与函数 2. 引用作为函数的返回值 函数返回值类型为引用型,在函数调用时,若接受返回值的是一个引用变量,相当于定义了一个对返回变量的引用。若接受返回值的是一个非引用变量,函数返回变量的值赋给接受变量。 如果函数返回值类型为引用型,则要求返回值为左值。这样,函数调用式可以当作左值。 1 2 3 4 5 6 7 8 9 10 11 12 13 /*********************************** * p5_12.cpp * * 使用引用作为函数的返回值 * ************************************/ #include <iostream> using namespace std; int max1(int a[ ],int n) //求数组a[ ]中元素的最大值 { int t=0; for(int i=0;i<n;i++) if(a[i]>a[t]) t=i; return a[t]+0; }
56
C++语言程序设计教程 第5章 构造数据类型 5.4.2 引用与函数 14 15 16 17 18 19 20 21 22 23 24 25 26 27 int& max2(int a[],int n) //求数组a[]中元素的最大值 { int t=0; for(int i=0;i<n;i++) if(a[i]>a[t]) t=i; return a[t]; } int& sum(int a[ ],int n) //求数组a[]中元素的和 int s=0; s+=a[i]; return s;
57
5.4.2 引用与函数 m1=10 m2=10 m3=10 m4=-858993460 -35 运行结果: 28 29 30 31 32
C++语言程序设计教程 第5章 构造数据类型 5.4.2 引用与函数 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 void main() { int a[10]={1,2,3,4,5,6,7,8,9,10}; int m1=max1(a,10); int m2=max2(a,10); int &m3=max2(a,10); int &m4=sum(a,10); cout<<"m1="<<m1<<endl; cout<<"m2="<<m2<<endl; cout<<"m3="<<m3<<endl; cout<<"m4="<<m4<<endl; m3+=10; max2(a,10)-=100; cout<<sum(a,10)<<endl; } int型变量接受函数返回的int &型值 运行结果: m1=10 m2=10 m3=10 m4= -35 通过引用变量修改返回变量的
58
5.4.3 常引用 常引用: 定义一个常引用后,就不能通过常引用更改引用的变量的值。 const 数据类型& 引用变量 = 变量名;
第5章 构造数据类型 5.4.3 常引用 常引用: 如果在定义引用变量时用const修饰,被定义的引用就是常引用。 定义常引用格式如下: 定义一个常引用后,就不能通过常引用更改引用的变量的值。 例如: int i (100); const int & r = i; 当试图使用r=200去更改i的值是错误的,但通过i本身可以改变i的值: 例如: i=200; 此时r的值变成200。 const 数据类型& 引用变量 = 变量名;
59
C++语言程序设计教程 第5章 构造数据类型 5.4.3 常引用 常引用类型常用作函数的形参类型,把形参定义为常引用类型时,这样在函数体内就不能通过形参改变实参的值,保证了函数调用时实参是“安全”的。这样的形参称为只读形参。 void fun(const int& x, int& y) { x=y; // 错误,x不可修改 y=x; } 注意: 形参为常引用类型时,实参可以是常量、变量表达式;但如果为非常引用类型时,实参必须为左值。对void fun(const int& x, int& y), 调用fun(100,200)是错误的, 调用fun(100,a)是正确的(a为变量)。
60
动态创建多维数组(注意数组越界) #include<iostream> namespace std void main()
第5章 构造数据类型 动态创建多维数组(注意数组越界) #include<iostream> namespace std void main() { float (*cp)[9][8]; int i,j,k; cp = new float[8][9][8]; for (i=0; i<8; i++) for (j=0; j<9; j++) for (k=0; k<8; k++) *(*(*(cp+i)+j)+k)=i*100+j*10+k; //通过指针访问数组元素
61
cout<<cp[i][j][k]<<" "; cout<<endl; }
第5章 构造数据类型 for (i=0; i<8; i++) { for (j=0; j<9; j++) { for (k=0; k<8; k++) //将指针cp作为数组名使用, //通过数组名和下标访问数组元素 cout<<cp[i][j][k]<<" "; cout<<endl; }
63
动态创建多维数组(注意数组越界) #include<iostream> namespace std void main()
第5章 构造数据类型 动态创建多维数组(注意数组越界) #include<iostream> namespace std void main() { float (*cp)[9][8]; int i,j,k; cp = new float[8][9][8]; for (i=0; i<8; i++) for (j=0; j<9; j++) for (k=0; k<9; k++) *(*(*(cp+i)+j)+k)=i*100+j*10+k; //通过指针访问数组元素
64
cout<<cp[i][j][k]<<" "; cout<<endl; }
第5章 构造数据类型 for (i=0; i<8; i++) { for (j=0; j<9; j++) { for (k=0; k<8; k++) //将指针cp作为数组名使用, //通过数组名和下标访问数组元素 cout<<cp[i][j][k]<<" "; cout<<endl; }
66
动态创建多维数组(注意数组越界) #include<iostream> namespace std void main()
第5章 构造数据类型 动态创建多维数组(注意数组越界) #include<iostream> namespace std void main() { float (*cp)[9][8]; int i,j,k; cp = new float[8][9][8]; for (i=0; i<8; i++) for (j=0; j<9; j++) for (k=0; k<9; k++) *(*(*(cp+i)+j)+k)=i*100+j*10+k; //通过指针访问数组元素
67
cout<<cp[i][j][k]<<" "; cout<<endl; }
第5章 构造数据类型 for (i=0; i<8; i++) { for (j=0; j<9; j++) { for (k=0; k<9; k++) //将指针cp作为数组名使用, //通过数组名和下标访问数组元素 cout<<cp[i][j][k]<<" "; cout<<endl; }
69
C++语言程序设计教程 第5章 构造数据类型 本章小结 ◇ 枚举类型实质是一组整型符号常量,其中的每个常量都可进行各种运算,对 一个枚举变量赋值时一定要类型一致。 ◇ 数组是一组同类型变量,可通过数组名加下标存取其中的单个变量。各个数 组元素在内存中顺序排列,数组名表示的是数组的起始地址。可以使用指针 运算符,用指针方式存取数组元素。 ◇ 一个多维数组是以低维数组为元素的数组,多维数组在内存中的排列与一维 数组相同,即从低地址到高地址顺序排列。数组名、高维名表示的是数组的 地址。 ◇ 数组名表示的地址不能是左值,但可以当作函数的形式参数,接受实参传送 的地址。 ◇ 以0作为结尾符的字符数组为字符串,数组大小与字符串长度的关系为: sizeof(s) =strlen(s)+1; ◇ 地址变量称为指针,所有地址变量的长度都是4个字节。 ◇ 地址变量存储数组的地址时(指针指向了数组),可通过指针名以指针方式 存取数组元素,也可将指针名当作数组名以数组方式存取元素。但指针比数 组多了一个存储地址的内存空间,因此指针名可以作为左值。
70
C++语言程序设计教程 第5章 构造数据类型 本章小结 ◇ 指针指向的动态申请的数组称为动态数组,动态数组所占的内存空间需要在 程序中进行释放。 ◇ 指针可作为函数的形参,接受实参传送的地址。 ◇ 引用是一个已存在变量的别名,它与被引用的对象共有内存单元,因此定义 一个引用型变量时一定要以一个已存在的变量作为初值。引用作为函数的 形参时,可用不赋初值,在函数体内引用变量代替实参进行运算,对形参 的改变反映到实参上。 ◇ 引用型函数返回对象被一个引用型变量接受,引用型变量成为返回对象的别 名;若被一个非引用变量接收,则将返回变量的值赋给接受变量。引用型函 数还可以的是一个左值,接受右值对象的值。 ◇ 常指针、常引用类型通常用作函数的形参,以防止在函数体内通过形参修改 实参指向的值,以保护实参。 ◇ 结构类型是各种已存在与已定义类型的组合体,同类型结构变量之间赋值等 同于每一个成员之间的赋值,其中数组成员的赋值等同于数组的拷贝。 ◇ 联合类型是各种已存在与已定义类型的共同体,联合变量各成员拥有共同的 内存空间。
Similar presentations