C程序设计
第10章 指针 指针是C语言中的一个重要概念,也是C语言的一个重要特色。正确而灵活地运用它,可以有效地表示复杂的数据结构;能动态分配内存;方便地使用字符串;有效而方便地使用数组;在调用函数时能获得1个以上的结果;能直接处理内存单元地址等,这对设计系统软件是非常必要的。掌握指针的应用,可以使程序简洁、紧凑、高效。每一个学习和使用C语言的人,都应当深入地学习和掌握指针。可以说,不掌握指针就是没有掌握C的精华。 指针的重要性 表示一些复杂的数据结构 快速的传递数据,减少了内存的耗用(重点) 使函数返回一个以上的值(重点) 能直接访问硬件 能方便的处理字符串 是理解面向对象语言中引用的基础
10.1 地址和指针的概念 变量的属性:名、值和地址 变量的访问方式:直接访问(通过变量名直接访问地址)、间接访问(把变量的地址放到另一变量中,使用时先找到后者的地址,再从中提取出前一变量的地址) 变量的地址称为该变量的“指针” 指针变量是存放地址的变量 指针的定义 地址:内存单元的编号 从零开始的非负整数 范围4G 指针 指针就是地址,地址就是指针 指针变量就是存放内存单元编号的变量,或者说指针变量就是存放地址的变量 指针和指针变量是两个不同的概念 但要注意:通常我们叙述是会把指针变量简称为指针,实际他们含义不同 指针的本质就是一个操作受限的非负整数
10.2 变量的指针和指向变量的指针变量 变量的指针就是变量的地址。存放变量地址的变量是指针变量,它用来指向另一个变量。为了表示指针变量和它所指向的变量之间的联系,在程序中用“*”符号表示“指向”。如果已定义i_pointer为指针变量,则(*i_pointer)是i_pointer所指向的变量。
10.2.1 定义一个指针变量 C语言规定所有变量在使用前必须定义,指定其类型,并按此分配内存单元。指针变量不同于整型变量和其他类型的变量,它是用来专门存放地址的,必须将它定义为“指针类型”。 定义指针变量的一般形式为 基类型 * 指针变量名; 可以用赋值语句使一个指针变量得到另一个变量的地址,从而使它指向一个该变量。 基本指针类型 int * p // p是变量的名字,int * 表示p变量存放的是int 类型变量的地址 // int * p;不表示定义了一个名字叫*p的变量 // int * p;应该这样理解:p是变量名, p变量的数据类型是int * 类型。所谓int * 类型,实际就是存放int变量地址的类型 int i=3; int j; p= &I 1 p保存了i的地址,因此指向了i; 2 p不是 i,i也不是p,更准确地说,修改p的值不影响i的值,修改i的值也不会影响p的值 3 如果一个指针变量指向某个普通变量则 * 指针变量 就完全等同于 普通变量 例如 如果p是个指针变量,并且p存放了普通变量i的地址 则p指向了普通变量 i * p 完全等同于普通变量I 或者说: 在所有出现*p的地方都可以替换成 i 在所有出项 i 的地方都可以替换成 * p * p 最准确的解释是: * p 表示的是以p的内容为地址的变量 j = *p; //等价于j= i
在定义指针变量时要注意两点: (1)指针变量前面的“*”表示该变量的类型为指针型变量 (2)在定义指针变量时必须指定基类型 补充程序: 1、指针热身程序_1.cpp 2、指针热身程序_2.cpp 3、指针常见错误_1.cpp 4、指针常见错误_2.cpp
10.2.2 指针变量的引用 指针变量中只能存放地址(指针),不要将一个整数(或其他非地址类型的数据)赋给一个指针变量。 有两个有关的运算符: (1)&:取地址运算符 (2)*:指针运算符(或称“间接访问”运算符),取其指向的内容。 int I =3; int * p; p= &I; printf("%d\n",(* p)++); //3 printf("%x\n", p); //13ff7c printf("%x\n", p++); //13ff7c printf(“%x\n”, p); //13ff7c+4个字节,等于13ff80 注意(*p)++和*(p++) 与 * (++p)的区别。
[例10.1]通过指针变量访问整型变量 #include <stdio.h> void main() { } int a, b; int * pointer_1, * pointer_2; a=100; b=10; pointer_1=&a; pointer_2=&b; printf(“%d, %d\n”, a, b); printf(“%d, %d\n”, *pointer_1, *pointer_2); }
对“&”和“*”运算符再作说明: 如果已执行了语句 pointer_1=&a; (1)&*pointer_1 (2)*&a 1、&和*两个运算符优先级相同,按从左向右方向结合,因此先进行 *point_1的运算,它就是变量a,再执行&运算,因此,& * pointer_1与&a等价; 2 、先进行&a运算,得a的地址,再进行*运算,即&a所指向的变量,也就是变量a。所以*&a等价于变量a; 3、等价于a++
[例10.2]输入a和b两个整数,按先大后小的顺序输出a和b #include <stdio.h> void main() { int * p1, * p2, * p, a, b; scanf(“%d, %d”, &a, &b); p1=&a; p2=&b; if(a<b) p1=&b; p2=&a; } printf(“a=%d, b=%d\n\n”, a, b); printf(“max=%d, min=%d\n”, *p1, *p2); 在这a和b并没有交换,仍然保持原值,但p1和p2的值改变了,p1由原来指向&a变为指向&b,p2有原来指向&b变为指向&a。因此这个算法并不是交换整型变量的值,而是交换两个指针变量的值。
10.2.3 指针变量作为函数参数 函数的参数不仅可以是整型、浮点型、字符型等数据,还可以是指针类型。它的作用是将一个变量的地址传送到另一个函数中。 如何通过被调函数修改主调函数普通变量的值 1.实参必须为该普通变量的地址; 2.形参必须为指针变量; 3.在被调用函数中通过 *形参名 = ……. 的方式就可以修改主调函数相关变量的值
[例10.3]题目要求同例10.2,即对输入的两个整数按大小顺序输出 #include <stdio.h> void main() { void swap(int * p1, int * p2); int a, b; int * pointer_1, *pointer_2; scanf(“%d, %d”, &a, &b); pointer_1=&a; pointer_2=&b; if(a<b) swap(pointer_1, pointer_2); printf(“\n%d, %d\n”,a, b); } void swap(int * p1, int * p2) { int temp; temp=*p1; *p1=*p2; *p2=temp; } 补充程序: 1 经典指针程序_互换两个数字.cpp(一定要补充) 2 指针使函数返回一个以上的值举例_1.cpp
[例10.4]输入a、b、c这3个整数,按大小顺序输出 void exchange(int *q1,int *q2,int * q3) { if(*q1<*q2) swap(q1,q2); if(*q1<*q3) swap(q1,q3); if(*q2<*q3) swap(q2,q3); } void swap(int *pt1,int * pt2) int temp; temp=*pt1; *pt1=*pt2; *pt2=temp; void main() { int a,b,c,*p1,*p2,*p3; scanf(“%d,%d,%d”,&a,&b,&c); p1=&a;p2=&b;pe=&c; exchange(p1,p2,p3); printf(“\n%d,%d,%d\n”,a,b,c); }
10.3 数组与指针 一个变量有地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元,它们都有相应的地址。指针变量既然可以指向变量,当然也可以指向数组元素(把某一元素的地址放到一个指针变量中)。所谓数组元素的指针就是数组元素的地址。 引用数组元素可以用下标法(如a[3]),也可以用指针法,即通过指向数组元素的指针找到所需的元素。使用指针法能使目标程序质量高(占内存少,运行速度快)。
10.3.1 指向数组元素的指针 定义一个指向数组元素的指针变量的方法,与以前介绍的指向变量的指针变量相同。 例如: int a[10]; int * p; p=&a[0]; p=a; //数组名是个指针常量,它指向数组首 元素地址 程序 /*int a[10]={0,1,2,3,4,5,6,7,8,9}; //int * p; //p=a; //p=&a[0]; printf(“%#x”,p); printf(“%#x”,a); printf(“%#x”, =&a[0;); printf("%d",&a[9]-&a[0]); 指针和数组 指针和一维数组 一维数组名是个指针常量 它存放的是一维数组第一个元素的地址 下标和指针的关系 p[i] 永远等价于 *(p+i) 确定一个一维数组需要几个参数 需要两个参数:数组第一个元素的地址和数组的长度 指针变量的运算 指针变量不能相加 不能相乘 不能相除 如果两个指针变量指向同一块连续空间中不同的存储单元,则这两个指针变量才可以相减 一个指针变量占几个字节? 假设p指向char类型变量(一个字节) 假设q指向int类型变量(4个字节) 假设r指向double类型变量(8个字节) 请问:p q r 本身作占的字节数是否一样? 答案应该是一样的。 总结: 一个指针变量,无论它所指向的变量占几个字节,该指针变量本身只占四个字节。 一个变量的地址是用该变量首字节的地址来表示
10.3.2 通过指针引用数组元素 假设p已定义为一个指向整型数据的指针变量,并已给它赋了一个整型数组元素的地址,使它指向某一个数组元素。如果有以下赋值语句: *p=1; 表示将1赋给p当前所指向的数组元素。
如果p的初值为&a[0],则: (1)p+i和a+i就是a[i]的地址。 (2)*(p+i)或*(a+i)是p+i或a+i所指向的数组元素,即a[i]。 (3)指向数组的指针变量也可以带下标,如p[i]与*(p+i)等价。 根据以上叙述,引用一个数组元素,可以用: (1)下标法,如a[i]形式; (2)指针法,如*(a+i)或*(p+i)。其中a是数组名,p是指向数组元素的指针变量,其初值p=a。 /*int a[10]={0,1,2,3,4,5,6,7,8,9}; int * p; for(p=a; p<a+10; p++) //p+1并不是将p的值(地址)简单加1,而是指指向下一个元素,应该是p+1*d(d代表p指向的变量存储类型的字节数) 如 int * p ,则p+1 ,意味着指向p+1*4(int占4个字节大小) //另外在这不能写a++,因为a数组名是个指针常量,它在运行期间是不变的。 { printf("%d ", *p); } printf("\n"); p=a; for(int i=0; i<10; i++) //printf("%d ", *(a+i)); printf("%d ", *(p+i)); printf("%d ",a[i]); printf("\n");*/ 补充程序: 1、数组_1.cpp 2、指针和下标的关系_1.cpp 3、一个指针变量到底占几个字节的问题.cpp 4、指针变量的运算_!.cpp
[例10.5]输出数组中全部元素 假设有一个a数组,整型,有10个元素。要输出各元素的值有3种方法。 (1)下标法 (2)通过数组名计算数组元素地址,找出元素的值 (3)用指针变量指向数组元素
[例10.6]通过指针变量输出a数组的10个元素 #include <stdio.h> void main() { } int * p, i, a[10]; p=a; for(i=0; i<10; i++) scanf(“%d”, p++); printf(“\n”); for(i=0; i<10; i++, p++) printf(“%d “, *p); } 这个程序是错误的。需要在第二次循环前加p=a。 一定搞清楚为什么在这需要再次赋值。原因是经过第一个for循环后,p已经指向a数组的末尾。因此在执行第二次循环时,p的起始值已经不是&a[0]了,而是a+10。由于执行第二个for循环时,每次要执行p++,因此p指向的a数组下面的10个元素,而这些存储单元的值是不可预料的。所以在这再次将p指向a数组元素的首地址
10.3.3 用数组名作函数参数 如果一个实参数组,要想在函数中改变此数组中的元素的值,实参与形参的对应关系有以下4种情况 (1)形参和实参都用数组名 (2)实参用数组名,形参用指针变量 (3)实参形参都用指针变量 (4)实参为指针变量,形参为数组名 [例10.7]将数组a中n个整数按相反顺序存放 [例10.8]用实参指针改写例10.7 补充程序: 1、确定一个一维数组需要几个参数_1.cpp 2、确定一个一维数组需要几个参数_2.cpp 3、确定一个一维数组需要几个参数_3.cpp
[例10.9]用选择法对10个整数按由大到小顺序排序
10.3.4 多维数组与指针 用指针变量可以指向一维数组中的元素,也可以指向多维数组中的元素。但在概念上和使用上,多维数组的指针比一维数组的指针要复杂一些。
1、多维数组元素的地址 int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}}; a是一个数组名。A数组包含三行,即3个元素:a[0]、a[1]、a[2]。而每一个元素又是一个一维数组,它包含4个元素(即4个列元素)。例如,a[0]所代表的一维数组又包含4个元素:a[0][0]、a[0][1]、a[0][2]、a[0][3]。可以认为二维数组是“数组的数组”,即二维数组a是由3个一维数组所组成的。 [例10.10]输出二维数组有关的值 A[3][2] A A[0] a[0][0] a[0][1] A[1] a[1][0] a[1][1] A[2] a[2][0] a[2][1] 理解为a数组有三个元素a[0] a[1] a[2],而a[0]又有两个元素a[0][0] a[0][1],a[1]又有两个元素a[1][0] a[1][1],a[2]又有两个元素a[2][0] a[2][1] ,而数组名代表的是该数组的第一个元素地址。在这有数组名A 和数组名a[0] a[1] a[2] 所以有: A 等价于 &a[0] 所以有 *(a+0) 等价于a[0] A+1 等价于 &a[1] *(a+1)等价于a[1] A+2 等价于 &a[2] *(a+2)等价于a[2] 即有*(a+i)等价于a[i] A[0]等价于&a[0][0] 等价于a+0 所以a[0]+1 等价于*(a+0)+1 等价于&a[0][1] a[0]+2等价于*(a+0)+2 等价于&a[0][2] A[1]等价于&a[1][0] 等价于a+1 a[1]+1等价于*(a+1)+1 等价于&a[1][1] a[1]+2等价于*(a+1)+2 等价于&a[1][2] A[2]等价于&a[2][0] 等价于a+2 a[2]+1等价于*(a+2)+1 等价于&a[2][1] a[2]+2 等价于*(a+2)+2 等价于&a[2][2] 即有 a[i]+j 等价于 *(a+i)+j 等价于 &a[i][j] 所以有 a[i][j] 等价于 *(*(a+i)+j)
2、指向多维数组元素的指针变量 (1)指向数组元素的指针变量 [例10.11]用指针变量输出二维数组元素的值 (2)指向由m个元素组成的一维数组的指针变量 [例10.12]输出二维数组任一行任一列的值
3、用指向数组的指针作函数参数(不要求) 一维数组名可以作为函数参数传递,多维数组名也可作函数参数传递。在用指针变量作形参以接受实参数组名传递来的地址时,有两种方法: 1、用指向变量的指针变量 2、用指向一维数组的指针变量 [例10.13]有一个班,3个学生,各学4门课,计算总平均分数以及第n个学生的成绩 [例10.14]在上题基础上,查找有一门以上课程不及格的学生,输出他们的全部课程的成绩
10.4 字符串与指针 10.4.1 字符串的表示形式 在C程序中,可以用两种方法访问一个字符串 (1)用字符数组存放一个字符串,然后输出该字符串。 [例10.15]定义一个字符数组,对它初始化,然后输出该字符串 (2)用字符指针指向一个字符串 [例10.16]定义字符指针 [例10.17]将字符串a赋值为字符串b [例10.18]用指针变量来处理例10.17问题
10.4.2 字符指针作函数参数 将一个字符串从一个函数传递到另一个函数,可以用地址传递的方法,即用字符数组名作参数,也可以用指向字符的指针变量作参数。在被调用的函数中可以改变字符串的内容,在主调函数中可以得到改变了的字符串。 [例10.19]用函数调用实现字符串的复制 (1)用字符数组作参数 (2)形参用字符指针变量 字符数组和字符指针的区别: Char sa[ ]=“this is a string” 字符数组sa是由若干元素构成的,每个元素放一个字符,有确定的地址; Char *sp =“this is a string” 字符指针是一个接受字符串首地址的变量,不能将字符串放到指针变量中去。 在对指针赋值前,它的值是不确定的。
10.4.3 对使用字符指针变量和字符数组的讨论 虽然用字符数组和字符指针变量都能实现字符串的存储和运算,但它们两者之间是有区别的,不应混为一谈,主要有以下几点。 (1)字符数组由若干个元素组成,每个元素中放一个字符,而字符指针变量中存放的是地址,决不是将字符串放到字符指针变量中。 (2)赋值方式。 (3)对字符指针变量赋初值。 (4)如果定义了一个字符数组,在编译时为它分配内存单元,它有确定的地址。而定义一个字符指针变量时,给指针变量分配内存单元,在其中可以放一个字符变量的地址,也就是说,该指针变量可以指向一个字符型数据,但如果未对它赋予一个地址值,则它并未具体指向一个确定的字符数据。 (5)指针变量的值是可以改变的。 [例10.20]改变指针变量的值 [例10.21]用带下标的字符指针变量引用字符串中的字符 (6)用指针变量指向一个格式字符串,可以用它代替printf函数中的格式字符串。 (2)(3) 问题的说明: char *a ; a=“I love China!” //正确的,因为在这的a是一个指针变量。赋给a的不是字符,而是字符串的第一个元素的地址。 Char a[14]; a =“I love China” //错误的,数组可以在定义时整体赋值,但不能再赋值语句中整体赋值,因为a是一个数组,a是数组名,是一个指针常量,因此不能将地址赋给数组名。 (4)问题的说明 scanf(“%s”,a); //不建议这样用,因为编译时虽然给a分配了内存单元,但a指向的地址单元没有定,所以a单元中是一个不可预料的值。 所以建议改为 char *a,str[20]; a=str; scanf(“%s”,a); //因为str是一个字符数组,编译时为它分配内存单元,已经有确定的地址。而a=str使得a存放了str的首地址,这样就a单元中的内容就是一个确定的值. 在讲完这一小节后补充动态内存分配的概念 补充程序: 1、 传统数组的缺陷_1.cpp 2、静态变量不能跨函数使用.cpp 3、malloc的使用_1.cpp 4、malloc的使用_2.cpp 5、动态一维数组示例.cpp 6、动态内存可以跨函数使用示例_1.cpp 动态内存分配 (书上296页) 传统数组的缺点: 1、数组长度必须实现制定,且只能是常整数,不能是变量 例题: int a[5] //ok int lent=5; int a[len] //错误 2、传统形式定义的数组,该数组的内存程序员无法手动释放,在一个函数运行期间,系统为该函数中数组所分配的空间会一直存在,直到该函数运行完毕时,数组的空间才会被系统释放。 3、数组的长度一旦定义,其长度就不能再更改,数组的长度不能再函数运行的过程中动态地扩成或缩小; 4、A函数定义的数组,在A函数运行期间可以被其他函数调用。但A函数运行完毕后,A函数中的数组将无法再被其他函数使用,即传统方式定义的数组不能被跨函数使用。 为什么需要动态分配内存 动态数组很好地解决了传统数组的这四个缺陷 传统数组也叫静态数组 动态内存分配举例——动态数组的构造 假设动态构造一个int型一维数组 int * p = (int *) malloc (int len); 本语句分配了两块内存,一块内存是动态分配的;总共len个字节,另一块是静态分配的,并且这块静态内存是 p变量本身所占的内存,总共占4个字节。 1、malloc只有一个int型的形参,表示要求系统分配的字节数; 2、malloc函数的功能是请求系统len个字节的内存空间,如果请求分配成功,则返回第一个字节的地址,如果分配不成功,则返回null malloc函数能且只能返回第一个字节的地址,所以我们需要把这个无任何实际意义的第一个字节的地址(俗称干地址)转化为一个有实际意义的地址,因此malloc之前的必须加(数据类型 *) ,表示把这个无实际意义的第一个字节的地址转化成为相应类型的地址。如:int *p =(int *)malloc (50),表示将系统分配好的50个字节的第一个字节的地址转化为int * 类型的地址,更准确地说是把第一个字节的地址转化成四个字节的地址,这样p就指向了第一个的四个字节,p+1就指向了第2个的四个字节,p+i就指向了第i+1的第四个字节。P[0]就是第一个元素,p[i]就是第i+1个元素。 double *p= (double *) malloc (80) 表示将系统分配好的 80个字节的第一个字节的地址转换为double *型的地址,更准确地说是把第一个字节的地址转化为8个字节的地址,这样p就指向了第一个的8个字节,p+1就指向了第2个的8个字节,p+i就指向了第i+1个的8个字节。P[0]就是第一个元素,p[i]即使第i+1的元素。 Free(p) 表示把p所指向的内存给释放掉,p本身的内存是静态的。 不能由程序员手动释放,p本身的内存只能在p变量所在的函数运行终止时由系统自动释放。 char * string; string=NULL; /* Allocate space */ string = (char *)malloc(5); if( string == NULL ) printf( "Insufficient memory available\n" ); else { *(string+0)='a'; *(string+1)='b'; *(string+2)='c'; *(string+3)='d'; *(string+4)='\0'; printf("%s\n", string); free( string ); printf( "Memory freed\n" ); } 静态内存和动态内存的比较 静态内存是由系统自动分配,由系统自动释放 静态内存是在栈中分配的。 动态内存是由程序员手动分配的,手动释放 动态内存是在堆中分配的。 跨函数使用内存的问题 静态内存不可以跨函数使用 所谓内存不可以跨函数使用,更准确地说是:静态内存在函数执行期间可以被其他函数使用,静态内存在函数执行完毕后就不能再被其他函数使用了 。 动态内存可以跨函数使用,动态内存在函数执行完毕后仍然可以被其他函数使用
10.5 指向函数的指针(不要求) 10.5.1 用函数指针变量调用函数 10.5 指向函数的指针(不要求) 10.5.1 用函数指针变量调用函数 可以用指针变量指向整型变量、字符串、数组,也可以指向一个函数。一个函数在编译时被分配给一个入口地址。这个函数的入口地址就称为函数的指针。可以用一个指针变量指向函数,然后通过该指针变量调用此函数。 [例10.22]求a和b中的大者 以下章节均不作要求
说明: (1)指向函数的指针变量的一般定义形式为 数据类型 (*指针变量名) (函数参数表列); (2)函数的调用可以通过函数名调用,也可以通过函数指针调用(即用指向函数的指针表量调用)。 (3)“int (*p)(int,int);”表示定义一个指向函数的指针变量p,它不是固定指向哪一个函数的,而是表示定义了这样一个类型的变量,它是专门用来存放函数的入口地址的。 (4)在给函数指针变量赋值时,只需给出函数名而不必给出参数。 (5)用函数指针变量调用函数时,只需将(*p)代替函数名即可,在(*p)之后的括号中根据需要写上实参。 (6)对指向函数的指针变量,像p+n、p++等运算是无意义的。
10.5.2 用指向函数的指针作函数参数 函数指针变量通常的用途之一是把指针作为参数传递到其他函数。函数的参数可以是变量、指向变量的指针变量、数组名、指向数组的指针变量等。函数的指针也可以作为参数,以实现函数地址的传递,这样就能够在被调用的函数中使用实参函数。 [例10.23]设一个函数process,在调用它的时候,每次实现不同的功能。输入a和b两个数,第一调用process时找出a和b中大者,第二次找出其中小者,第三次求a与b之和
10.6 返回指针值的函数 一个函数可以返回一个整型值、字符值、实型值等,也可以返回指针型的数据,即地址。其概念与以前类似,只是返回的值的类型是指针类型而已。 这种返回指针值的函数,一般定义形式为 类型名 * 函数名(参数表列); [例10.24]有若干个学生的成绩(每个学生有4门课程),要求在用户输入学生序号后,能输出该学生的全部成绩。用指针函数来实现 [例10.25]对上例中的学生,找出其中不及格课程的学生及其学生号
10.7 指针数组和指向指针的指针 10.7.1 指针数组的概念 一个数组,若其元素均为指针类型数据,称为指针数组,也就是说,指针数组中的每一个元素都相当于一个指针变量。一维指针数组的定义形式为 类型名 * 数组名[数组长度]; 例如:int * p[4]; [例10.26]将若干字符串按字母顺序输出 Char *p[4]; 定义一个4个元素的字符指针数组p,其中每个数组元素是一个字符指针,可以指向一个字符串。也就是说利用此字符指针数组可以指向4个字符串。 指针数组用得最多的是”字符型指针数组”,利用字符指针数组可以指向多个长度不等的字符串,使字符串处理更加方便、灵活,节省内存空间。 使用字符型指针数组指向多个字符串与使用两维字符数组存储多个字符串的比较:(见书上269页图10-40的对比) 节省存储空间(二维数组要按最长的串开辟存储空间) 便于对多个字符串进行处理,节省处理时间。(使用指针数组排序各个串不必移动字符数据,只要改变指针指向的地址)
10.7.2 指向指针的指针 在掌握了指针数组的概念的基础上,下面介绍指向指针数据的指针变量,简称为指向指针的指针。 [例10.27]使用指向指针的指针 [例10.28]指针数组的元素指向整型数据 补充程序: 1、多级指针.cpp 2、多级指针2.cpp 多级指针 int i=10; int * p= &I ; // p只能存放int 类型变量的地址 int **q=&p; //q是int *类型,所谓int ** 类型就是q只能存放int * 类型变量的地址; int ***r= &q; //r是int **类型,所谓int ***类型就是指r只能存放int ** 类型变量的地址; //r= &p; //错误的,因为r是int ***类型,r只能存放int ** 类型变量的地址 printf(“i=%d\n”,***r); //输出结果是10,只有***r才表示的是i,*r 或者是***r代表的都不是I
10.7.3 指针数组作main函数的形参 指针数组的一个重要应用是作为main函数的形参。在以往的程序中,main函数的第一行一般写成以下形式: void main() 括号中是空的。实际上,main函数可以有参数,例如: void main(int argc, char * argv[])
10.8 有关指针的数据类型和指针运算的小结 10.8.1 有关指针的数据类型的小结 定义 含义 int i; 定义整型变量i int * p; p为指向整型数据的指针变量 int a[n]; 定义整型数组a,它有n个元素 int * p[n]; 定义指针数组p,它由n个指向整型数据的指针元素组成 int (*p)[n]; p为指向含n个元素的一维数组的指针变量 int f(); f为返回整型函数值的函数 int * p(); p为返回一个指针的函数,该指针指向整型数据 int (*p)(); p为指向函数的指针,该函数返回一个整型值 int ** p; p是一个指针变量,它指向一个指向整型数据的指针变量
10.8.2 指针运算小结 (1)指针变量加(减)一个整数 (2)指针变量赋值 (3)指针变量可以有空值,即该指针变量不指向任何变量 (4)两个指针变量可以相减 (5)两个指针变量比较 (1) p++ p-- p+I 等都是表示指针变量加(减)一个整数,但并不是简单将指针变量的原值加(减)一个整数,而是将该指针变量的原值(是一个地址)和它所指向的变量所占用的内存单元字节数相加(减)。如p+i表示地址计算p+c*i,c为字节数 (2)p276 (3)p=null;其中null是整数0,它使p的存储单元中所有二进制位均为0,也就是使p指向地址为0的单元。系统保证使该单元不作它用,即有效数据的指针不指向0单元。 (4)如果两个指针变量都指向同一数组,则这两个指针变量之差是两个元素之间的元素个数。 (5)如果两个指针指向同一个数组的元素,可以进行比较。如p1<p2
10.8.3 void指针类型 ANSI新标准增加了一个“void”指针变量,即可定义一个指针变量,但不指定它是指向哪一种类型数据的。ANSI C标准规定用动态存储分配函数时返回void指针,它可以用来指向一个抽象的类型的数据,在将它的值赋给另一指针变量时要进行强制类型转换使之适合于被赋值的变量的类型。
小结 在本章中介绍了指针的基本概念和初步应用。应该说明,指针是C语言中重要的概念,是C的一个特色。使用指针的优点:(1)提高程序效率;(2)在调用函数时变量改变了的值能够为主调函数使用,即可以从函数调用得到多个可改变的值;(3)可以动态存储分配。 但是同时应该看到,指针使用实在太灵活,对熟练的程序员来说,可以利用它编写出颇有特色的、质量优良的程序,实现许多用其他高级语言难以实现的功能,但也十分容易出错,而且这种错误往往比较隐蔽。由于指针运用的错误可能会使整个程序遭受破坏,比如由于未对指针变量p赋值就向*p赋值,就可能破坏了有用的单元的内容。有人说指针是有利有弊的工具,如果使用指针不当,会出现隐蔽的、难以发现和排除的故障。因此,使用指针要十分小心谨慎,要多上机调试程序,以弄清一些细节,并积累经验。