1.29k likes | 1.43k Views
第七章 指针. 教 材 : C 程序设计导论. 主 讲 : 谭 成 予 nadinetan@163.com. 武汉大学计算机学院. 指针定义和引用. 地址运算. 多级指针. 数组与指针关系、字符指针. 指针做函数参数、命令行参数. 本讲重点. 指针的优点. C 程序设计中使用指针可以 : 使程序 简洁 、 紧凑 、 高效 有效 地表示复杂的数据结构 动态 分配内存 得到 多于一个 的函数返回值. 内存. 0. …. 2000. 2001. 2002. 2003. 2005.
E N D
第七章 指针 教 材: C程序设计导论 主 讲: 谭 成 予 nadinetan@163.com 武汉大学计算机学院
指针定义和引用 地址运算 多级指针 数组与指针关系、字符指针 指针做函数参数、命令行参数 本讲重点
指针的优点 C程序设计中使用指针可以: • 使程序简洁、紧凑、高效 • 有效地表示复杂的数据结构 • 动态分配内存 • 得到多于一个的函数返回值
内存 0 …... 2000 2001 2002 2003 2005 …... 内存中每个字节有一个编号-----地址 指针与地址 • 变量与地址 程序中: int i; float k; i 编译或函数调用时为其分配内存单元 k 变量是对程序中数据 存储空间的抽象
指针与指针变量 • 指针:一个变量的地址; • 指针变量:专门存放变量地址的变量。
…... 整型变量i 2000 10 2001 2002 2003 变量i_pointer 2004 2005 2006 …... 指针与指针变量 指针 变量的内容 变量的地址 2000 指针变量
变量地址(指针) 指针变量 地址存入 指针变量 指向 变量值 变量 指针与指针变量
…... 整型变量i 2000 10 2001 2002 2003 变量i_pointer 2004 2000 指针变量 2005 2006 …... 直接访问与间接访问 • 直接访问:按变量地址存取变量值 • 间接访问:通过存放变量地址的变量去访问变量 例i=3; -----直接访问 3 20 例 *i_pointer=20; -----间接访问
i *i_pointer i_pointer 变量i &i i_pointer 2000 3 i=3; *i_pointer=3 *i_pointer 指针变量的定义和引用 • 指针变量的定义 • 一般形式: [存储类型] 数据类型 *指针名; 表示定义指针变量 不是‘*’运算符 合法标识符 指针变量本身的存储类型 指针的目标变量的数据类型
例 int *p1,*p2; float *q ; static char *name; 指针变量的定义和引用 注意: 1、int *p1, *p2; 与 int *p1, p2; 2、指针变量名是p1,p2 ,不是*p1,*p2 3、指针变量只能指向定义时所规定类型的变量 4、指针变量定义后,变量值不确定,应用前必须先赋值
例int *p=&i; int i; 例 int i; int *p=&i; 赋给指针变量, 不是赋给目标变量 变量必须已说明过 类型应一致 • 指针变量的初始化 一般形式:[存储类型] 数据类型 *指针名=初始地址值; 例 int i; int *p=&i; int *q=p; 用已初始化指针变量作初值 例int main( ) { int i; static int *p=&i; .............. } () 不能用auto变量的地址 去初始化static型指针
#include<stdio.h> int main() { int i=10; int *p; *p=i; printf("%2d",*p); return 0; } …... 整型变量i 2000 10 2001 2002 2003 指针变量p 2004 2005 随机 2006 …... 指针变量必须先赋值,再使用 危险! #include<stdio.h> int main( ) { int i=10,k; int *p; p=&k; *p=i; printf("%2d",*p); return 0; }
零指针与空类型指针 • 零指针:(空指针) • 定义:指针变量值为零 • 表示: int * p=0; p指向地址为0的单元, 系统保证该单元不作它用 表示指针变量值没有意义 #define NULL 0 int *p=NULL:
零指针与空类型指针 • p=NULL与未对p赋值不同 • 用途: • 避免指针变量的非法引用 • 在程序中常作为状态比较 例 int *p; ...... while(p!=NULL) { ...… }
零指针与空类型指针 表示不指定p是指向哪一种 类型数据的指针变量 • void *类型指针 • 表示: void *p; • 使用时要进行强制类型转换 例 char *p1; void *p2; p1=(char *)p2; p2=(void *)p1;
空指针到底是什么? 每一种指针类型都有一个特殊值—— “空指针” :它与同类型的其它所有指针值都不相同, 它“与任何对象或函数的指针值都不相等”。对malloc() 的成功调用也不会返回空指针, 如果失败, malloc() 的确返回空指针, 这是空指针的典型用法:表示“未分配” 或者“尚未指向任何地方” 的指针。 空指针不同于未初始化的指针。 空指针可以确保不指向任何对象或函数; 未初始化指针则可能指向任何地方。 每种指针类型都有一个空指针, 而不同类型的空指针的内部表示可能不尽相同。尽管程序员不必知道内部值, 但编译器必须时刻明确需要那种空指针, 以便在需要的时候加以区分。
*i_pointer i_pointer 10 2000 &i_pointer i …... 整型变量i 2000 10 2001 2002 2003 变量i_pointer 2004 2000 指针变量 2005 2006 …... 含义: 取指针所指向变量的内容 单目运算符 优先级: 2 结合性:自右向左 含义: 取变量的地址 单目运算符 优先级: 2 结合性:自右向左 • 两者关系:互为逆运算 • 理解 指针变量的引用:&与*运算符 • 含义 i_pointer &i &(*i_pointer) i *i_pointer *(&i) i_pointer = &i = &(*i_pointer) i = *i_pointer = *(&i) i_pointer-----指针变量,它的内容是地址量 *i_pointer----指针的目标变量,它的内容是数据 &i_pointer---指针变量占用内存的地址
…... 整型变量i 2000 10 2001 整型变量k 2002 2003 变量i_pointer 2004 2005 2000 指针变量 2006 …... 例k=i; --直接访问 k=*i_pointer; --间接访问 例k=i; k=*i_pointer; 10
…... 整型变量a f86 10 f87 指针变量pa f88 f86 f89 f8a f8b f8c …... a:10 *pa:10 &a:f86(hex) pa:f86(hex) &pa:f88(hex) 例 指针的概念 #include<stdio.h> int main() { int a; int *pa=&a; a=10; printf("a:%d\n",a); printf("*pa:%d\n",*pa); printf("&a:%x(hex)\n",&a); printf("pa:%x(hex)\n",pa); printf("&pa:%x(hex)\n",&pa); return 0; }
…... 2000 指针变量p1 2002 2004 指针变量p 指针变量p2 整型变量a 整型变量b 2006 2008 …... 例7.1 输入两个数,并使其从大到小输出. #include<stdio.h> int main() { int *p1,*p2,*p,a,b; scanf("%d,%d",&a,&b); p1=&a; p2=&b; if(a<b) { p=p1; p1=p2; p2=p;} printf("a=%d,b=%d\n",a,b); printf("max=%d,min=%d\n",*p1,*p2); return 0; } 2008 2006 2006 2008 2006 5 9 5,9 max=9,min=5
动态存储分配 • 实际编程中可能遇到所需内存空间无法预先确定的情况,需要根据实际的数据的多少来确定。 • C语言提供动态分配内存函数 (stdlib.h)。 • malloc()函数 void *malloc(unsigned int size); 功能:在动态存储区域中分配一个size字节的连续空间。 函数的返回值为指针,它的值是所分配的存储区域的起始地址。 如没有足够的存储空间分配,则返回0(记为NULL)值。
动态存储分配 /*L7-2.C:动态分配内存范例*/ #include <stdio.h> #include <stdlib.h> char count,*ptr,*p; int main() { ptr=(char *)malloc(30*sizeof(char)); if(ptr==NULL){ puts("memory allocation error."); return (1); } p=ptr; for(count=65;count<91;count++) *p++=count; *p='\0'; puts(ptr); free(ptr); system("PAUSE"); return (0); } ABCD……XYZ
calloc()函数 calloc函数的原型为:void *calloc(unsigned int n, unsigned int size); 函数的功能是:在动态存储区域中分配n个为size字节的连续空间,并将该存储空间自动置初值0。函数的返回值为指针,它的值是所分配的存储区域的起始地址。如分配不成功,则返回0值。 n 表示对象的个数 size 每个对象占用内存单元的字节数 动态分配内存
#include <stdio.h> #include <stdlib.h> int main(){ unsigned num; int *ptr,I,*p; printf("enter the number of type int to allocate:"); scanf("%d",&num); ptr=(int *)calloc(num,sizeof(int)); if(ptr!=NULL){ puts("memory allocation was successful."); p=ptr; for(I=0;I<num;I++) *p++=I; for(I=0;I<num;I++) printf("%d",ptr[I]); printf("\n"); } else puts("memory allocation error."); free(ptr); return (0); } enter the number of type int to allocate:20 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
动态分配内存 • free()函数 void free(void *ptr) ; 函数的功能是:释放由ptr所指向的存储空间。ptr的值必须是malloc或calloc函数返回的地址。此函数无返回值。
动态分配内存 realloc函数realloc函数的原型为:void *realloc(void *ptr,size_t size); • realloc函数的作用是对p所指向的存储区进行重新分配即改变大小; • ptr:是已经由malloc或calloc函数分配的存储区的指针; • size:是重新分配的存储区的大小(字节数),新存储区包含着和旧存储区相同的内容,如果新存储区较大,则新增加的部分未被初始化。 • 此函数返回值是新存储区的首地址;如果没有足够的内存空间则返回NULL,此时旧存储区内容不变。
/*L7-3.C*/ #include <stdio.h> #include <string.h> #include <stdlib.h> int main(void) { char *p; /*定义字符指针*/ p=malloc(19); /*申请30个字节的存储区*/ if(!p){ /*检查返回指针的有效性*/ printf(“Allocation error\n”); exit(1); } /*指针无效,终止程序运行*/ strcpy(p,”this is an example”); printf(“%x,%s\n”,p,p); p=realloc(p,20); /*申请重新分配存储区*/ if (!p) { printf(“Allocation error\n”); exit(1); } strcat(p,”.”); printf(“%x,%s\n”,p,p); /*输出字符串首地址和内容*/ free(p); /*释放存储区*/ return 0; } 动态存储分配函数的使用举例
动态存储分配函数的使用举例 /*L7-4.C*/ #include <stdio.h> #include <stdlib.h> int *get_mem(void) { int *p; p=calloc(100,sizeof(int)); if (!p) { printf(“Allocation error\n”); exit(1); /*指针无效,终止程序运行*/ } return p; }
free() 怎么知道有多少字节需要释放? malloc/free 的实现会在分配的时候记下每一块的 大小, 所以在释放的时候就不必再考虑了。 calloc() 和malloc() 有什么区别?利用calloc的零填充功能安全吗?free() 可以释放calloc()分配的内存吗, 还是需要一个cfree()? calloc(m, n) 本质上等价于p = malloc(m * n); memset(p, 0, m * n); 填充的零是全零, 因此不能确保生成有用的空指针值或浮点零值。 free() 可以安全地用来释放calloc() 分配的内存。
指针定义和引用 地址运算 多级指针 数组与指针关系、字符指针 指针做函数参数、命令行参数 本讲重点
指针的运算 • 指针变量的赋值运算 • p=&a; (将变量a地址p) • p=array; (将数组array首地址p) • p=&array[i]; (将数组元素地址p) • p1=p2; (指针变量p2值p1) • 不能把一个整数p, 也不能把p的值整型变量 • 所有类型的指针都可置为NULL 如int i, *p; p=1000; () i=p; () 指针变量与其指向的变量具有相同数据类型
指针的算术运算 • pi p id (i为整型数,d为p指向的变量所占字节数) • p++, p--, p+i, p-i, p+=i, p-=i等 • 若p1与p2指向同一数组,p1-p2=两指针间元素个数(p1-p2)/d • p1+p2 无意义
a数组 p a[0] p+1,a+1 a[1] a[2] a[3] a[4] p+i,a+i a[5] a[6] a[7] a[8] p+9,a+9 a[9] 指针的算术运算 例 p指向float数,则 p+1 p+1 4 例 p指向int型数组,且p=&a[0]; 则p+1 指向a[1] 1 例 int a[10]; int *p=&a[2]; p++; *p=1; 例 int a[10]; int *p1=&a[2]; int *p2=&a[5]; 则:p2-p1=3;
指针变量的关系运算 • 若p1和p2指向同一数组,则: • p1<p2 表示p1指的元素在前; • p1>p2 表示p1指的元素在后; • p1==p2表示p1与p2指向同一元素。 • 若p1与p2不指向同一数组,比较无意义; • p==NULL或p!=NULL
stack[0] p1 p1 p1 stack[1] stack[2] …… 例 7.5 堆栈(stack)是一种先进后出(first-in last-out)的表,好比将若干个盘子堆放起来,每次放或者取一个盘子,最先堆放的盘子最后被取走,将一个数据压入堆栈称为入栈,从堆栈中取走一个数据称为出栈操作。现在编程实现该算法。 push(10) 10 push(3) 3 pop() pop()
#include <stdio.h> #include <stdlib.h> #define SIZE 50 void push(int i); /*入栈函数使用说明*/ int pop(void); /*出栈函数使用说明*/ int *tos,*p1,stack[SIZE]; /*堆栈、栈顶及栈底指针定义*/ int main(void) { int value; tos=stack; p1=stack; do{ printf(“输入一个整数:“); scanf(“%d”,&value); if (value!=0) push(value); else printf(“出栈数据是%d\n”,pop()); }while(value!=-1); return 0; }
/*将入栈操作定义成用户自定义函数*/ void push(int i) { p1++; if(p1==(tos+SIZE)) /*判断堆栈是否已满*/ { printf(“堆栈已满\n”); exit(1); } *p1=i; } /*将出栈操作定义成自定义函数*/ int pop(void) { if(p1==tos) /*判断堆栈是否空*/ { printf(“堆栈空\n”); exit(1); } p1--; return *(p1+1); }
指针定义和引用 地址运算 多级指针 数组与指针关系、字符指针 指针做函数参数、命令行参数 本讲重点
指针变量与数组 指向数组元素的指针变量 p array[0] array[1] array[2] array[3] ... array[9] 整型指针p &array[0] 例 int array[10]; int *p; p=&array[0]; // p=array; 或 int *p=&array[0]; 或 int *p=array; 数组名是表示数组首地址的地址常量, 就是数组的指针。
地址 p[0] a[0] a[0] a[0] *p *a 元素 地址 元素 *(p+1) *(a+1) p[1] a[1] a[1] a[1] *(p+2) *(a+2) p[2] a[2] a[2] a[2] p a a[3] a[3] p+1 a+1 p+2 a+2 a[9] a[9] p[9] *(p+9) *(a+9) a[9] ... ... p+9 a+9 指针法 下标法 数组元素表示方法 [] 变址运算符 a[i] *(a+i) a[i] p[i] *(p+i) *(a+i)
pa a[0] 1 a[1] 2 a[2] 3 a[3] 4 a[4] 5 例7.6数组元素的引用方法 #include <stdio.h> int main() { int a[5],*pa,i; for(i=0;i<5;i++) a[i]=i+1; pa=a; for(i=0;i<5;i++) printf("*(pa+%d):%d\n",i,*(pa+i)); for(i=0;i<5;i++) printf("*(a+%d):%d\n",i,*(a+i)); for(i=0;i<5;i++) printf("pa[%d]:%d\n",i,pa[i]); for(i=0;i<5;i++) printf("a[%d]:%d\n",i,a[i]); return 0; }
例 int a[]={1,2,3,4,5,6,7,8,9,10},*p=a,i; 数组元素地址的正确表示:(A)&(a+1) (B)a++ (C)&p (D)&p[i] 数组名是地址常量 p++,p-- () a++,a-- () a+1, *(a+2) ()
a 0 5 1 8 p p 2 7 3 6 4 2 5 7 6 3 例7.7注意指针变量的运算 例 int main() { int a []={5,8,7,6,2,7,3}; int y,*p=&a[1]; y=(*--p)++; printf(“%d ”,y); printf(“%d”,a[0]); return 0; } 6 5 6
a 0 5 1 8 p p p p p p p p 2 7 3 6 4 2 5 7 6 3 例7.8注意指针的当前值 #include <stdio.h> int main() { int i,*p,a[7]; p=a; for(i=0;i<7;i++) scanf("%d",p++); printf("\n"); for(i=0;i<7;i++,p++) printf("%d",*p); return 0; } p=a; 指针变量可以指到数组后的内存单元
一级指针变量与一维数组的关系 int *p与int q[10] • 数组名是指针(地址)常量 • p=q; p+i 是q[i]的地址 • 数组元素的表示方法:下标法和指针法, 即若p=q, 则 p[i] q[i] *(p+i) *(q+i) • 形参数组实质上是指针变量,即int q[ ] int *q • 在定义指针变量(不是形参)时,不能把int *p 写成int p[]; • 系统只给p分配能保存一个指针值的内存区(一般2字节);而给q分配2*10字节的内存区
在C语言中“指针和数组等价”到底是什么意思?在C语言中“指针和数组等价”到底是什么意思? 不表示它们相同, 甚至也不能互换。 可以用指针方便的访问数组或者模拟数组。 等价的基础:一旦数组出现在表达式中, 编译器会隐式地生成一个指向数组第一个成员地指针, 就像程序员写出了&a[0] 一样。 例外的情况是, 数组为sizeof 或&操作符的操作数, 或者为字符数组的字符串初始值。 这个定义的后果:编译器不严格区分数组下标操作符和指针。 a[i], 根据上边的规则, 数组蜕化为指针然后按照指针变量的方式如p[i] 那样寻址, 尽管最终的内存访问并不一样。如果你把数组地址赋给指针:p = a; 那么p[3] 和a[3] 将会访问同样的成员。
string string[0] I string[1] string[2] l string[3] o string[4] v string[5] e string[6] string[7] C string[8] h string[9] i string[10] n string[11] a string[12] ! string[13] \0 指针与字符串 字符串 • 字符数组 #include <stdio.h> int main( ) { char string[]=“I love China!”; printf(“%s\n”,string); printf(“%s\n”,string+7); return 0; }
string I l o string v e C h i n a ! \0 用字符指针实现 字符指针初始化:把字符串首地址赋给string char *string; string=“I love China!”; #include <stdio.h> int main( ) { char *string=“I love China!”; printf(“%s\n”,string); string+=7; while(*string) { putchar(string[0]); string++; } return 0; } *string!=0
字符指针变量与字符数组的区别 char *cp;与char str[20]; • str由若干元素组成,每个元素放一个字符;而cp中存放字符串首地址 • char str[20]; str=“I love China!”; () char *cp; cp=“I love China!”; () • str是地址常量;cp是地址变量 • cp接受键入字符串时,必须先开辟存储空间 改为: char *cp,str[10]; cp=str; scanf(“%s”,cp); () 例 char str[10]; scanf(“%s”,str); () 而 char *cp; scanf(“%s”, cp); () 或改为: char *cp; cp=malloc(20) ; scanf(“%s”,cp); ()
如果字符指针在定义是初始化,如:char *s=”I am a teacher”;以后使用字符指针s时,字符串的长度不能超过”I am a teacher”的长度,否则数组溢出,而编译程序不进行这种错误的检查,将可能造成“死机”或者程序结果错误。 字符指针变量与字符数组的区别 • 字符数组名str是非左值表达式,而字符指针pstr是左值表达式。因而以下表达式是正确的:char str[20],*pstr=str; • pstr++使pstr指向下一个字符下列表达式是错误的:str++数组名是常量,不能参与自增或自减运算 • str=”I am a teacher”不能向数组名赋值