第6单元指针 -110 第6单元指针 本单元教学目标 介绍C++中指针的基本概念 学习要求 指针是C++中最重要的基本概念之一。要求同学们充分理解和掌握: 1什么是地址?什么是指针? 2指针类型变量的声明方法和怎样将一个变量或数组的地址赋给指针类型的变量 3.怎样通过指针类型的变量去访问某个变量或数组元素的值。 授课内容 61地址与指针 我们知道,从拓扑结构上来说,计算机的内存储器就象一个巨大的一维数组,每个数组 元素就是一个存储单元(在微型计算机中其大小通常为一个字节)。就象数组中的每个元素 都有一个下标一样,每个内存单元都有一个编号,又称地址 存储器是程序活动的基本舞台。在运行一个程序时,程序本身及其所用到的数据都要 放在内存储器中:程序、函数、变量、常数、数组和对象等,无不在内存储器中占有一席之 凡是存放在内存储器中的程序和数据都有一个地址,用它们占用的那片存储单元中的 第一个存储单元的地址表示。在C++中,为某个变量或者函数分配存储器的工作由编译程 序完成l。在编写程序时,通常是通过名字来使用一个变量或者调用某个函数。这样做既直 观,又方便。而变量和函数的名字与其实际存储地址之间的变换由编译程序自动完成。但是 C+-也允许直接通过地址处理数据,在很多情况下这样做可以提高程序的运行效率 那么怎样才能知道某个变量、数组或者函数的地址呢? C++规定: (1)变量的地址可以使用地址运算符&求得。例如,&x表示变量x的地址 1实际上,为变量和函数分配存储的工作是分几步完成的。在编译和连接的过程中,只给程序中的各个变量 分配了相对地址。每个变量的实际存储位置要到程序运行时才能确定。如果是局部变量,其存储分配更晚, 要到该局部变量所属的函数被调用时才进行。因此,同一个变量,在程序的各次运行中可能被分配在不同的 存储地址上。这也是为什么通常只需要知道某变量确有一个地址,而不必关心该地址值具体是多少的原因
第 6 单元 指针 - 110 - 第 6 单元 指针 本单元教学目标 介绍C++中指针的基本概念。 学习要求 指针是C++中最重要的基本概念之一。要求同学们充分理解和掌握: 1.什么是地址? 什么是指针? 2.指针类型变量的声明方法和怎样将一个变量或数组的地址赋给指针类型的变量; 3.怎样通过指针类型的变量去访问某个变量或数组元素的值。 授课内容 6.1 地址与指针 我们知道, 从拓扑结构上来说, 计算机的内存储器就象一个巨大的一维数组, 每个数组 元素就是一个存储单元(在微型计算机中其大小通常为一个字节)。就象数组中的每个元素 都有一个下标一样, 每个内存单元都有一个编号, 又称地址。 内存储器是程序活动的基本舞台。在运行一个程序时, 程序本身及其所用到的数据都要 放在内存储器中:程序、函数、变量、常数、数组和对象等, 无不在内存储器中占有一席之 地。 凡是存放在内存储器中的程序和数据都有一个地址, 用它们占用的那片存储单元中的 第一个存储单元的地址表示。在C++中,为某个变量或者函数分配存储器的工作由编译程 序完成1。在编写程序时, 通常是通过名字来使用一个变量或者调用某个函数。这样做既直 观, 又方便。而变量和函数的名字与其实际存储地址之间的变换由编译程序自动完成。但是, C++也允许直接通过地址处理数据, 在很多情况下这样做可以提高程序的运行效率。 那么怎样才能知道某个变量、数组或者函数的地址呢? C++规定: (1) 变量的地址可以使用地址运算符&求得。例如, &x 表示变量 x 的地址; 1 实际上, 为变量和函数分配存储的工作是分几步完成的。在编译和连接的过程中, 只给程序中的各个变量 分配了相对地址。每个变量的实际存储位置要到程序运行时才能确定。如果是局部变量, 其存储分配更晚, 要到该局部变量所属的函数被调用时才进行。因此, 同一个变量, 在程序的各次运行中可能被分配在不同的 存储地址上。这也是为什么通常只需要知道某变量确有一个地址, 而不必关心该地址值具体是多少的原因
第6单元指针 (2)数组的地址,即数组第一个元素的地址,可以直接用数组名表示 (3)函数的地址用函数名表示。 前面已经介绍过,所谓地址就是存储单元在整个内存构成的数组中的下标,实际上可用 一个无符号整数(无符号长整数)表示2。因此,可以用一个无符号整型变量将地址存放起 来。这种用来存放地址的变量就叫作指针型变量,简称指针。假定pt为一指针,则语句 ptr=&x 就将变量x的地址存入了指针ptr中。当然,也可以通过指针得到变量x的值,或者改变变 量x的值: y= ptr 其中的运算符“*”用于一个地址(例如指针变量值)之前,表示该地址上的变量、数 组或者函数本身。因此,语句*p=2,的作用相当于x=2,因为指针p中存放者变量x 的地址。由于通过指针可以对一个其他类型的变量进行操作,所以有时也把“变量x的地址 存放在指针ptr中”简称为“指针ptr指向变量x”。 62指针型变量的声明 如上所述,指针也是一个变量,因此也必须遵循先声明,后使用的原则。在C+中,并 没有一个专用的指针类型说明符,声明指针类型的变量是通过声明该指针指向的变量类型 进行的。如果要声明一个用于存储int类型变量地址的指针ptr,则可以使用如下语句 注意这时运算符“*”的含义是说明指针变量pr用于指向一个int类型的变量 例6-编写用于交换两个整型变量的值的函数。 算法:交换两个变量的值必须使用第3个变量。例如,要交换变量x和y的内容 可以使用临时变量tmp int x, y, tmp 但在例5-6中直接利用上法将函数swap()编写为 //函数swap0:交换两个整形变量的值(不成功) void swap( int x. int int tmp; 2这只是一个简化了的说法,在不同的计算机中可能有更复杂的情况
第 6 单元 指针 - 111 - (2) 数组的地址, 即数组第一个元素的地址, 可以直接用数组名表示; (3) 函数的地址用函数名表示。 前面已经介绍过,所谓地址就是存储单元在整个内存构成的数组中的下标,实际上可用 一个无符号整数(无符号长整数)表示2。因此,可以用一个无符号整型变量将地址存放起 来。这种用来存放地址的变量就叫作指针型变量, 简称指针。假定 ptr 为一指针, 则语句 ptr = &x; 就将变量 x 的地址存入了指针 ptr 中。当然, 也可以通过指针得到变量 x 的值,或者改变变 量 x 的值: *ptr = 2; y = *ptr; 其中的运算符“*”用于一个地址(例如指针变量值)之前,表示该地址上的变量、数 组或者函数本身。因此, 语句 *ptr = 2; 的作用相当于 x = 2; ,因为指针 ptr 中存放者变量 x 的地址。由于通过指针可以对一个其他类型的变量进行操作, 所以有时也把“变量 x 的地址 存放在指针 ptr 中”简称为“指针 ptr 指向变量 x”。 6.2 指针型变量的声明 如上所述, 指针也是一个变量, 因此也必须遵循先声明, 后使用的原则。在C++中, 并 没有一个专用的指针类型说明符, 声明指针类型的变量是通过声明该指针指向的变量类型 进行的。如果要声明一个用于存储 int 类型变量地址的指针 ptr, 则可以使用如下语句: int *ptr; 注意这时运算符“*”的含义是说明指针变量 ptr 用于指向一个 int 类型的变量。 [例 6-1] 编写用于交换两个整型变量的值的函数。 算 法: 交换两个变量的值必须使用第 3 个变量。例如,要交换变量 x 和 y 的内容, 可以使用临时变量 tmp: int x, y, tmp; tmp = x; x = y; y = tmp; 但在例 5-6 中直接利用上法将函数 swap()编写为 // 函数 swap(): 交换两个整形变量的值(不成功) void swap(int x,int y) { int tmp; 2 这只是一个简化了的说法, 在不同的计算机中可能有更复杂的情况
第6单元指针 tmp =X 却是有问题的。试用以下主函数验证 //试验函数 swapo用的主函数 void maino int x =2,y=3 cout<"x=“<x<<“,y=“<y<<endl (x,y) cout < After exchange x& y: "< endl cout(“x=“<x<“,y=“<y<<endl 其输出为 After exchange x& y 2,y=3 从结果可以看出,上面的swap()函数根本没有完成交换变量x和y的任务。为什么?请 看图6-1中的内存分配情况 DS:1001 主函数中定义的变量x DS:1002 主函数中定义的变量y DS:1003 2 函数 swap o的参数变量x DS:1005 DS:100 函数 swapO的参数变量y DS:1007 DS:1008 函数 swapo中定义的局部变量tm DS:1009 图6-1验证交换变量值的函数swap()时的内存分配示意图3 图6-1描述的是程序运行到swap()函数中时的内存分配。由图中可以看出,主函数 中声明的变量x和y与函数swap()的参数x和y在内存中分别占有各自的存储区,它们之 间唯一的联系只是在主函数中调用函数swap()时将主函数中变量x和y的值分别传送给 了函数swap()的两个参数x和y。参数x和y在函数swap()运行期间相当于两个局部 3由于地址分配问题相当复杂,超出本课程的范围。因此图中的地址值只是示意。以下各图同此
第 6 单元 指针 - 112 - tmp = x; x = y; y = tmp; } 却是有问题的。试用以下主函数验证: // 试验函数 swap()用的主函数 void main() { int x = 2, y = 3; cout << "x = “ << x << “, y = “ << y << endl; swap(x,y); cout << "After exchange x & y:” << endl; cout << “x = “ << x << “, y = “ << y << endl; } 其输出为 x = 2, y = 3 After exchange x & y: x = 2, y = 3 从结果可以看出, 上面的 swap()函数根本没有完成交换变量 x 和 y 的任务。为什么? 请 看图 6-1 中的内存分配情况: 图 6-1 验证交换变量值的函数 swap()时的内存分配示意图3 图 6-1 描述的是程序运行到 swap()函数中时的内存分配。由图中可以看出,主函数 中声明的变量 x 和 y 与函数 swap()的参数 x 和 y 在内存中分别占有各自的存储区, 它们之 间唯一的联系只是在主函数中调用函数 swap()时将主函数中变量 x 和 y 的值分别传送给 了函数 swap()的两个参数 x 和 y。参数 x 和 y 在函数 swap()运行期间相当于两个局部 3 由于地址分配问题相当复杂,超出本课程的范围。因此图中的地址值只是示意。以下各图同此。 DS:1000 DS:1001 DS:1002 DS:1003 DS:1004 DS:1005 DS:1006 DS:1007 DS:1008 DS:1009 主函数中定义的变量x 主函数中定义的变量y 函数swap()的参数变量x 函数swap()的参数变量y 函数swap()中定义的局部变量tmp 2 3 2 3
第6单元指针 113 变量。因此,事情很明显,在函数swap()执行完毕以后,其参变量ⅹ和y的值确实已被交 换,如图6-2所示 然而,事情到此为止D:1002 了。从图62中可以看出,DS:1001 主函数中定义的变量x 虽然swap()函数的两个 DS:1002 主函数中定义的变量y 参数变量x和y的值已被交 函数swap0的参数变量x 换,但原来主函数的变量xDS:1005 和y的值却没有发生变化 DS:1002 函数 swapo的参数变量y 而且,随着函数swap()运DS:1008 行结束返回主函数后, swap DS:1092 函数 swapo中定义的局部变量tmp ()中为局部工作变量申请 的内存空间,包括其参数图6-2函数sWap()中的运算结束时的内存分配示意图 x、y和局部变量tmp占用 的内存单元,都将被释放。函数swap()所做的一切工作都付之东流了 那么,怎样才能编写一个真正可以交换参数值的swap()函数呢?这就要用到指针了 下面将函数swap()的参数声明为指向int类型的指针,重新编写该函数 程序 ∥/ Example6-1:函数 swapo:交换两个整形变量的值 void swap (int *xp int *yp) int tmp *xp * yp *kyp tmp: DS:1000 注意到这次函数DS:1001 主函数中定义的变量x DS:1002 swap()的参数变量xpDs:1003 主函数中定义的变量y 和yp是两个指向整型变m:041100 函数swap0的参数变量xp 量的指针,因此在调用该DS:1005 函数时只能使用变量的地s:10071002函数swap0的参数变量yp DS:1006 址作为实参。使用下列语DS:1008 函数 swap o中定义的局部变量tmp 句取代测试主函数中的函DS:1009 数调用语句 图6-3函数 swapo中的运算结束时的内存分配示意图 swap(&x, &y) 此时的内存分配如图6-3所示 于是,在新的swap()函数中,语句
第 6 单元 指针 - 113 - 变量。因此, 事情很明显, 在函数 swap()执行完毕以后, 其参变量 x 和 y 的值确实已被交 换, 如图 6-2 所示。 然而, 事情到此为止 了。从图 6-2 中可以看出, 虽然 swap()函数的两个 参数变量x和y的值已被交 换, 但原来主函数的变量 x 和 y 的值却没有发生变化。 而且, 随着函数 swap()运 行结束返回主函数后, swap ()中为局部工作变量申请 的内存空间, 包括其参数 x、y 和局部变量 tmp 占用 的内存单元,都将被释放。函数 swap()所做的一切工作都付之东流了。 那么, 怎样才能编写一个真正可以交换参数值的 swap()函数呢? 这就要用到指针了。 下面将函数 swap()的参数声明为指向 int 类型的指针, 重新编写该函数: 程 序: // Example 6-1:函数 swap(): 交换两个整形变量的值 void swap(int *xp,int *yp) { int tmp; tmp = *xp; *xp = *yp; *yp = tmp; } 注意到这次函数 swap()的参数变量 xp 和 yp 是两个指向整型变 量的指针, 因此在调用该 函数时只能使用变量的地 址作为实参。使用下列语 句取代测试主函数中的函 数调用语句: swap(&x,&y); 此时的内存分配如图 6-3 所示。 于是, 在新的 swap()函数中, 语句 tmp = *xp; DS:1000 DS:1001 DS:1002 DS:1003 DS:1004 DS:1005 DS:1006 DS:1007 DS:1008 DS:1009 主函数中定义的变量x 主函数中定义的变量y 函数swap()的参数变量x 函数swap()的参数变量y 函数swap()中定义的局部变量tmp 2 3 3 2 2 图6-2 函数swap()中的运算结束时的内存分配示意图 DS:1000 DS:1001 DS:1002 DS:1003 DS:1004 DS:1005 DS:1006 DS:1007 DS:1008 DS:1009 主函数中定义的变量x 主函数中定义的变量y 函数swap()的参数变量xp 函数swap()的参数变量yp 函数swap()中定义的局部变量tmp 2 3 1000 1002 2 图6-3 函数swap()中的运算结束时的内存分配示意图
第6单元指针 114 kyp tmp 通过地址直接对主函数中原来的变量x和y进行操作,完成了交换这两个变量的值的任务 在函数swap()执行完毕后,即使释放其局部变量tmp和指针参数xp、y占用的存储也不 会影响到主函数中变量x和y的新内容 般来说,函数可以用返回值的形式为调用程序提供一个计算结果。在前面的各单元中 出现的函数返回值类型大都是int、 double之类的简单类型。其实,也可以将一个地址数据 (如变量、数组和函数的地址,指针变量的值等)作为函数的返回值。在声明返回值为地址的 函数时,要使用指针类型说明符,例如 char *strchr(char *string, int c) char *strstr(char *stringl, char *string2) 这是两个用于字符串处理的库函数,其返回值均为地址。前者的功能为在字符串 string 中查找字符c,如果字符串 string中有字符c出现,则返回字符c的地址,否则返回NUI 后者的功能为在字符串 stringl中查找子字符串 string2,如果字符串 stringl中包含有子字符串 string2,则返回sing2在 string中的地址(即 string2中第一个字符的地址)否则返回空指针 值N 例6-2将表示月份的数值(1-12)转换成对应的英文月份名称。 算法:首先声明一个字符串数组 month,用来存放月份的英文名称。在转换时只须 按下标值返回一个字符串的地址即可。 程序 / Example6-2:将月份数值转换为相应的英文名称 char month static char *month[ Illegal month",∥/月份值错 January /一月 February /二月 March 三月 /四月 /五月 /六月 July /七月 /八月 /九月 /十月 /十一月
第 6 单元 指针 - 114 - *xp = *yp; *yp = tmp; 通过地址直接对主函数中原来的变量 x 和 y 进行操作, 完成了交换这两个变量的值的任务。 在函数 swap()执行完毕后,即使释放其局部变量 tmp 和指针参数 xp、yp 占用的存储也不 会影响到主函数中变量 x 和 y 的新内容。 一般来说, 函数可以用返回值的形式为调用程序提供一个计算结果。在前面的各单元中 出现的函数返回值类型大都是 int、double 之类的简单类型。其实,也可以将一个地址数据 (如变量、数组和函数的地址, 指针变量的值等)作为函数的返回值。在声明返回值为地址的 函数时, 要使用指针类型说明符, 例如 char *strchr(char *string, int c); char *strstr(char *string1, char *string2); 这是两个用于字符串处理的库函数,其返回值均为地址。前者的功能为在字符串 string 中查找字符 c,如果字符串 string 中有字符 c 出现, 则返回字符 c 的地址, 否则返回 NULL。 后者的功能为在字符串 string1 中查找子字符串 string2,如果字符串 string1 中包含有子字符串 string2, 则返回 string2 在 string1 中的地址(即 string2 中第一个字符的地址), 否则返回空指针 值 NULL。 [例 6-2] 将表示月份的数值(1−12)转换成对应的英文月份名称。 算 法:首先声明一个字符串数组 month,用来存放月份的英文名称。在转换时只须 按下标值返回一个字符串的地址即可。 程 序: // Example 6-2:将月份数值转换为相应的英文名称 char *month_name(int n) { static char *month[]= { "Illegal month", // 月份值错 "January", // 一月 "February", // 二月 "March", // 三月 "April", // 四月 "May", // 五月 "June", // 六月 "July", // 七月 "August", // 八月 "September", // 九月 "October", // 十月 "November", // 十一月