1 / 109

第 7 章 间接访问 — 指针

第 7 章 间接访问 — 指针. 指针的概念 指针运算与数组 动态内存分配 字符串再讨论 指针作为函数参数和返回值 指针数组与多级指针 指向多维数组的指针 指向函数的指针. 本章将介绍 C++ 语言的一个重要的特性:指针,为了成为一个优秀的 C++ 语言程序员,你必须掌握指针并熟练地使用它们。. 指针介绍. 指针 是内存的 地址 并可作为数据 是一个灵活和危险的机制 允许 共享 处理数据 允许内存 动态 分配(只要需要,而非预先定义). 指针的概念. 指针就是把地址作为数据处理 指针变量:存储地址的变量

todd
Download Presentation

第 7 章 间接访问 — 指针

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 第7章 间接访问—指针 • 指针的概念 • 指针运算与数组 • 动态内存分配 • 字符串再讨论 • 指针作为函数参数和返回值 • 指针数组与多级指针 • 指向多维数组的指针 • 指向函数的指针

  2. 本章将介绍C++语言的一个重要的特性:指针,为了成为一个优秀的C++语言程序员,你必须掌握指针并熟练地使用它们。本章将介绍C++语言的一个重要的特性:指针,为了成为一个优秀的C++语言程序员,你必须掌握指针并熟练地使用它们。 指针介绍 • 指针 • 是内存的地址并可作为数据 • 是一个灵活和危险的机制 • 允许共享处理数据 • 允许内存动态分配(只要需要,而非预先定义)

  3. 指针的概念 • 指针就是把地址作为数据处理 • 指针变量:存储地址的变量 • 变量的指针:当一个变量存储另一个变量的地址时,那我们说它就是那个变量的指针 • 使用指针的目的:提供间接访问

  4. x 1000 2 1000 p 指针的概念 续 • 如在某一程序中定义了 int x = 2; • 如系统给x分配的空间是1000号单元,则指向x的指针是另一个变量p,p中存放的数据为1000 • 1000号单元的内容有两种访问方式: • 访问变量x(直接访问) • 访问变量p指向的单元的内容(间接访问)

  5. 定义指针变量 • 定义指针变量要告诉编译器该变量中存放的是一个地址。 • 指针变量的主要用途是提供间接访问,因此也需要知道指针指向的单元的数据类型 • 指针变量的定义 类型标识符 *指针变量; 如:int *intp; double *doublep; int *p, x, *q;

  6. 指针变量的操作 • 如何让指针指向某一变量?因为我们不知道系统分配给变量的真正地址是什么。 • 用地址运算符 “&”解决。如表达式 “&x”返回的是变量 x的地址。如:intp = &x; • & 运算符后面不能跟常量或表达式。如 &2是没有意义的,&(m * n + p )。也是没有意义的 • 如何通过指针变量处理和改变它所指向的单元的值? • 用引用运算符 “*” 解决。如 *intp表示的是 intp指向的这个单元的内容。如:*intp = 5 等价于 x = 5 • 在对 intp使用引用运算之前,必须先对 intp赋值

  7. X X 8 3 1000 1000 Y Y 4 4 1004 1004 intp intp 1000 1000 指针实例 如执行: *intp=Y+4; 如有: int X, *intp, Y; X=3; Y=4; intp=&X; 注意:不能用 intp=100; 因为我们永远不知道变量存储的 真实地址,而且程序每次运行变量地址可能都不同。

  8. 指针使用 • 指针变量可以指向不同的变量。如上例中intp指向x,我们可以通过对intp的重新赋值改变指针的指向。如果想让intp指向y,只要执行intp=&y就可以了。这时,intp与x无任何关系。 • 同类的指针变量之间可相互赋值,表示二个指针指向同一内存空间。 • 空指针 • 指针没有指向任何空间 • 空指针用常量NULL表示,NULL的值一般赋为0 • 不能引用空指针指向的值

  9. 指针变量的使用 设有定义 int x, y; int *p1,*p2; 执行语句: x=23; y=234; 执行语句: p1=&x; p2=&y; 执行语句: *p1=34; p2=p1;

  10. A p1 a B p2 b 解: A B p1 a p1 a B B p2 b p2 b 指针实例 有以下结构 比较执行 p1=p2和 *p1= *p2后的不同结果。

  11. 指针的初始化 • 指针在使用前必须初始化。 • 和别的变量一样,定义指针不初始化是一个比较普通的错误。 • 没有初始化的指针可能指向任意地址,对这些指针作操作可能会导致程序错误。 • NULL是一个特殊指针值,称为空指针。它的值为0。它可被用来初始化一个指针,表示不指向任何地址。 • 思考:int *p; *p = 5; 有什么问题?

  12. 第7章 间接访问—指针 • 指针的概念 • 指针运算与数组 • 动态内存分配 • 字符串再讨论 • 指针作为函数参数和返回值 • 指针数组与多级指针 • 指向多维数组的指针 • 指向函数的指针

  13. 指针运算和数组 • 指向数组元素的指针 • 数组元素是一个独立的变量,因此可以有指针指向它。如:p = &a[1], p = &a[i] • 数组元素的地址是通过数组首地址计算的。如数组的首地址是 1000,则第i 个元素的地址是1000 + i * 每个数组元素所占的空间长度

  14. 指针与数组 • 在C++中,指针和数组关系密切,几乎可以互换使用 • 数组名可以看成是常量指针,对一维数组来说,数组名是数组的起始地址,也就是第0个元素的地址 • 如执行了p=array,则p与array是等价的,对该指针可以进行任何有关数组下标的操作

  15. 例如:有定义 int a[10], *p 并且执行了 p=a, 那么可用下列语句访问数组a的元素 for ( i=0; i<10; ++i ) cout << p[i];

  16. 指针运算 指针保存的是一个地址,地址是一个整型数,因此可以进行各种算术运算,但仅有加减运算是有意义的。指针运算与数组有密切的关系 • 指针+1表示数组中指针指向元素的下一元素地址; • 指针-1表示数组中指针指向元素的上一元素地址; • 合法的指针操作:p + k, p - k, p1 - p2

  17. 数组元素的指针表示 • 当把数组名,如 intarray,赋给了一个同类指针intp 后,intarray 的元素可以通过intp访问。第i个元素的地址可表示为 intp + i,第i个元素的值可表示为 *(intp + i)。 • 通过指针访问数组时,下标有效范围由程序员自己检查。 • 如输出数组 a 的十个元素

  18. 方法2: for ( i=0; i<10; ++i ) cout << *(a+i); 方法1: for ( i=0; i<10; ++i ) cout << a[i]; 方法3: for ( p=a; p<a+10; ++p ) cout <<*p ; 方法4: for ( p=a, i=0; i<10; ++i ) cout <<*(p+i); 方法5: for ( p=a, i=0; i<10; ++i ) cout <<p[i] ; 下列程序段 有无问题? for ( i=0; i<10; ++i ) { cout <<*a ; ++a; }

  19. array intp 指针和数组的区别 • 虽然通过指针可以访问数组,但两者本质是不同的。 • 在定义数组时为数组的各个元素分配了全部的存储区,而在定义指针时,仅仅分配四个字节的存储区存放指针地址。只有把一个数组名付给了对应的指针后,指针才能当作数组使用 • 如有:int array[5], *intp; 当执行了 intp = array 后

  20. 第7章 间接访问—指针 • 指针的概念 • 指针运算与数组 • 动态内存分配 • 字符串再讨论 • 指针作为函数参数和返回值 • 指针数组与多级指针 • 指向多维数组的指针 • 指向函数的指针

  21. 动态分配 • 在C++语言中,每个程序需要用到几个变量,在写程序前就应该知道。每个数组有几个元素也必须在写程序时就决定。 • 有时我们并不知道我们需要多大的数组元素直到程序开始运行。因此希望能在程序中根据某一个当前运行值来决定数组的大小。如设计一个打印魔阵的程序,我们希望先输入魔阵的阶数,然后根据阶数定义一个矩阵

  22. 动态分配方法 • 这些问题的解决方案就是内存的动态分配。我们定义一个指针,并让它指向一个合适的内存。如: int *scores; scores = 内存的起始地址;

  23. 动态内存分配与回收 • C++中由new 和 delete两个运算符替代- 运算符new用于进行内存分配: 申请动态变量:p = new type;申请动态数组:p = new type[size]; 申请动态变量并初始化:p = new type(初值); - 运算符delete 释放 new分配的内存: 释放动态变量:delete p; 释放动态数组:delete [] p;

  24. 动态内存分配与回收 //为简单变量动态分配内存,并作初始化 int main() { int *p; p = new int(99); //动态分配内存,并将99作为初始化值赋给它 cout<< *p; delete p; return 0; }

  25. 动态内存分配与回收 //动态字符串的使用 int main() {int *p; char *q; p = new int(5); q = new char[10]; strcpy(q, "abcde"); cout << *p << endl; cout << q << endl; delete p; delete q; return 0; } 输出结果: 5 abcde

  26. 动态分配的检查 • new操作的结果是申请到的空间的地址 • 当系统空间用完时,new操作可能失败 • new操作失败时,返回空指针

  27. 动态内存分配与回收 //动态分配检查 int main() {int *p; p = new int; if(!p) { cout << "allocation failure\n"; return 1;} *p = 20; cout << *p; delete p; return 0; }

  28. assert宏 • assert()宏在标准头文件cassert中 • assert()有一个参数,表示断言为真的表达式,预处理器产生测试该断言的代码。如果断言不是真,则在发出一个错误消息后程序会终止。

  29. #include <iostream> #include <cassert> //包含assert宏的头文件 using namespace std; int main() { int *p; p = new int; assert (p != 0); //p等于0,则退出程序 *p=20; cout << *p; delete p; return 0; }

  30. 内存分配的进一步介绍 • 静态分配:对全局变量和静态变量,编译器为它们分配空间,这些空间在整个程序运行期间都存在 • 自动分配:函数内的局部变量空间是分配在系统的栈工作区。当函数被调用时,空间被分配;当函数执行结束后,空间被释放 • 动态分配:在程序执行过程中需要新的存储空间时,可用动态分配的方法向系统申请新的空间,当不再使用时用显式的方法还给系统。这部分空间是从被称为堆的内存区域分配。

  31. 动态变量是通过指针间接访问的。如果该指针被修改,这个区域就被丢失了。堆管理器认为你在继续使用它们,但你不知道它们在哪里,这称为内存泄露。动态变量是通过指针间接访问的。如果该指针被修改,这个区域就被丢失了。堆管理器认为你在继续使用它们,但你不知道它们在哪里,这称为内存泄露。 • 为了避免出现孤立的区域,应该明白地告诉堆管理器这些区域不再使用。可以采用delete操作,它释放由new申请的内存。 • 当释放了内存区域,堆管理器重新收回这些区域,而指针仍然指向堆区域,但不能再使用指针指向的这些区域。 • 要确保在程序中同一个区域释放一次。 • 释放内存对一些程序不重要,但对有些程序很重要。如果你的程序要运行很长时间,而且存在内存泄漏,这样程序会耗尽所有内存,直至崩溃。 内存泄漏

  32. 动态空间分配示例 • 输入一批数据,计算它们的和。数据个数在设计程序时尚无法确定。 • 存储一批数据应该用数组,但C++语言的数组大小必须是固定的。该问题有两个解决方案: • 开设一个足够大的数组,每次运行时只使用一部分。缺点:浪费空间 • 用动态内存分配根据输入的数据量申请一个动态数组

  33. #include <iostream> using namespace std; int main() { int *p, i, n, sum=0; cout << "An array will be created dynamically.\n\n" ; cout << "Input an array size n followed by n integers:"; cin >> n; if (!(p = new int [n])) exit(1); for (i=0; i<n; ++i) cin >> p[i]; for(i=0; i<n; ++i) sum += p[i]; delete [] p; cout << "Number of elements:" << n << endl; cout << "Sum of the elements:" << sum << endl; return 0; } 可改为: p = new int [n]; assert( p != NULL);

  34. 第7章 间接访问—指针 • 指针的概念 • 指针运算与数组 • 动态内存分配 • 字符串再讨论 • 指针作为函数参数和返回值 • 指针数组与多级指针 • 指向多维数组的指针 • 指向函数的指针

  35. 字符串再讨论 • 字符串的另一种表示是定义一个指向字符的指针。然后直接将一个字符串常量或字符串变量赋给它 • 如 char *String, ss[ ] =“abcdef”; String = “abcde”; String = ss;

  36. OS Program “abcde” 数据段 或代码区 String 栈 堆 String = “abcde”;的执行结果 • 字符串常量存储在一个称为数据段的内存区域里 • 将存储字符串”abcde”的内存的首地址赋给指针变量String。

  37. OS Program 数据段 String “abcdef\0” 栈 堆 String = ss的执行过程 将字符数组ss的起始地址存入String

  38. OS Program 数据段 String 栈 “aaa\0” 堆 String = new char[5];strcpy(String, “aaa”) • 动态变量存储在堆工作区 • 将存储字符串”aaa”的内存的首地址赋给指针变量String。

  39. 用指针表示的字符串的操作 • 可以直接作为字符串操作函数的参数。但必须注意,如果该指针指向的是一个字符串常量时,则使用是受限的。如不能作为strcpy的第一个参数 • 由于在C++中,数组名被解释成指向数组首地址的指针。因此,字符串是用一个指针变量表示,我们可以把此指针变量解释成数组的首地址,通过下标访问字符串中的字符。如string[3]的值是d。

  40. 用指针处理串 • 目的:编写一个记录串中单词的个数的函数。 • 关键技术:要传递一个字符串给函数

  41. 字符串作为函数的参数 • 字符串作为函数的参数和数组名作为参数传递一样,可以有两种方法 • 作为字符数组传递 • 作为指向字符的指针传递 • 两种传递方式的本质是一样的,都是传递了字符串的首地址 • 字符串作为字符数组传递时不需要指定长度。因为字符串操作的结束是依据‘\0’

  42. 统计字符串中单词数的函数 #include <ctype> Using namespace std; int word_cnt(const char *s) { int cnt = 0; while (*s != '\0') { while (isspace(*s)) ++s; //跳过空白字符 if (*s != '\0') { ++cnt; //找到一个单词 while (!isspace(*s) && *s != '\0') ++s; //跳过单词 } } return cnt; }

  43. 第7章 间接访问—指针 • 指针的概念 • 指针运算与数组 • 动态内存分配 • 字符串再讨论 • 指针作为函数参数和返回值 • 指针数组与多级指针 • 指向多维数组的指针 • 指向函数的指针

  44. 指针作为函数参数和返回值 • 指针作为函数参数 • 数组名作为函数参数 • 返回指针的函数 • 引用和引用传递 • 返回引用的函数

  45. 指针作为函数参数 例:编一函数,交换二个参数值。 新手可能会编出如下的函数: void swap(int a, int b) { int c; c=a; a=b; b=c; } 希望通过调用swap(x, y)交换变量x和y的值 因为C++采用的是值传递机制,函数中a、b值的交换不会影响实际参数x和y的值

  46. x y 3 4 a b 正确的方法 void swap(int *a, int *b) { int c; c=*a; *a= *b; *b=c; } 交换x和y的值,可以调用swap(&x, &y) 用指针作为参数可以在函数中修改主调程序的变量值,即实现变量传递。必须小心使用!!!

  47. 能解一元二次方程的函数 • 目前为止我们了解到的函数只能有一个返回值,由return 语句返回。 • 一个一元二次方程有二个解,如何让此函数返回二个解。答案是采用指针作为函数的参数。 • 由调用程序准备好存放两个根的变量,将变量地址传给函数。在函数中将两个根的值分别放入这两个地址

  48. 函数原型 • 函数原型可设计为: void SolveQuadratic(double a, double b, double c, double *px1, double *px2) • 函数的调用 SolveQuadratic(1.3, 4.5, 2.1, &x1, &x2) SolveQuadratic(a, b, c, &x1, &x2) • 函数的参数有两类:输入参数和输出参数。一般,输入参数用值传递,输出参数用指针传递。在参数表中,输入参数放在前面,输出参数放在后面。

  49. 原型的改进 • 并不是每个一元二次方程都有两个不同根,有的可能有两个等根,有的可能没有根。函数的调用者如何知道x1和x2中包含的是否是有效的解? • 解决方案:让函数返回一个整型数。该整型数表示解的情况

  50. 完整的函数 int SolveQuadratic(double a,double b,double c, double *px1,double *px2) { double disc, sqrtDisc; if(a == 0) return 3; //不是一元二次方程 disc = b * b - 4 * a * c; if( disc < 0 ) return 2; //无根 if ( disc == 0 ) { *px1 = -b /(2 * a); return 1;} //等根 //两个不等根 sqrtDisc = sqrt(disc); *px1 = (-b + sqrtDisc) / (2 * a); *px2 = (-b - sqrtDisc) / (2 * a); return 0; }

More Related