380 likes | 533 Views
第 9 章、指针. 主讲教师:陈荣钦 QQ : 241881888. 本章介绍. 指针基本概念 指针变量 指针与数组 指针数组 动态内存分配 多级指针 指针参数 函数指针. 为何需要指针. 直接访问内存,避免大块数据复制,高效 通过形参获得返回值 按需分配内存,用多少申请多少. 指针是 C 语言的精髓,不懂 C 语言中的指针就不懂什么是 C 语言。. 内存. 教学楼. 地址. 存储单元. 0. …. …. 50. 2000. 数据. 2001. 2002. 2003. 2004. 2005. 402. 101. 102.
E N D
第9章、指针 主讲教师:陈荣钦 QQ:241881888
本章介绍 • 指针基本概念 • 指针变量 • 指针与数组 • 指针数组 • 动态内存分配 • 多级指针 • 指针参数 • 函数指针
为何需要指针 • 直接访问内存,避免大块数据复制,高效 • 通过形参获得返回值 • 按需分配内存,用多少申请多少 指针是C语言的精髓,不懂C语言中的指针就不懂什么是C语言。
内存 教学楼 地址 存储单元 0 …... …... 50 2000 数据 2001 2002 2003 2004 2005 402 101 102 201 301 302 502 202 501 601 602 401 …... …... 教室号码 指针基本概念 教室
内存 0 …... …... a的地址 2000 2001 2002 2003 b的地址 2004 2005 …... …... 变量地址 short a;//2个字节 float b;//4个字节 a 系统为每个变量都分配内存,每个内存单元的起始地址称为变量的地址! b 变量是对存储空间的抽象
…... short变量a 2000 10 2001 2002 2003 2004 指针变量p 2005 2006 …... 指针变量 • 指针变量:也是一个变量,这个变量里存放另一个变量的地址 short a = 10; 称为指针p指向变量a 2000 指针变量定义: 类型 *标识符; short *p = &a;
*p p &p 10 2000 a …... 变量a 2000 2001 10 2002 2003 2000 2004 指针变量p 2005 2006 …... &与*运算符 short a = 10; short *p; //指针变量 p = &a; printf(“%d”, *p); 区别: short *p;//这是定义指针 p=&a; //a的地址赋给p,表明p指向a *p----获得目标变量,这里是a &p---这是指针变量p本身的地址 • 两者互为逆运算 p =&a=&(*a) a=*p=*(&a)
…... 整型变量a 2000 2001 10 2002 2003 2004 指针变量p 2000 2005 2006 …... 通过指针修改目标变量 short a = 10; short *p = &a; 20 10 *p = 20; //*p即为变量a
指针变量初始化 • 初始化格式: [存储类型] 数据类型 *指针名 = 初始地址值; int i; int *p = &i; short *p2 = &i; //错 变量必须已说明过,类型应一致
… 整型变量a 2000 …... …... 指针变量p 指针变量q …... 指针赋值 20 int a = 20 ; 2000 int *p, *q; p = &a; 2000 q = p;
零指针与空类型指针 • 零指针:(空指针) #define NULL 0 int *p = NULL: int * p = NULL; //即指针值为0 int *p; ...... while (p != NULL) {...… } • p = NULL与未对p赋值不同 • 用途: • 避免指针变量的非法引用 • 在程序中常作为状态比较 • void *类型指针(空类型指针) • 表示: void *p; • p不指向某种类型的变量 • 使用时要进行强制类型转换 例char *p1; void *p2; p1=(char *)p2;
pi可操作单元 … a 2000 …... …... pi pc …... 不同类型的指针操作同一内存变量 pc可操作单元 #include <stdio.h> int main ( ) { unsigned short a; unsigned short *pi = &a; char *pc = (char *)&a; *pi = 0XF0F0; *pc = 0; printf ("a = %X", a); } F0 00 F0 2000 2000 输出结果: a = F000
a a[0] 2000 a a[1] 2002 a+9 a+1 a+2 a[2] 2004 …... …... …… a[9] 2018 a+k &a[k] *(a+k) a[k] 指针与数组 数组名是数组的起始地址,也就是第一个元素的地址。 short a[10];
元素 地址 p a[0] 2000 *a p[0] *p 2000 a a[1] p 2002 a+1 a+9 a+2 a[2] *(p+1) p[1] *(a+1) p+1 2004 p+2 *(a+2) *(p+2) p[2] …... …... …… …… …… …… …… a[9] 2018 p+9 p[9] *(a+9) *(p+9) 指向数组的指针 将数组首地址赋给指针变量p,那么p就是指向数组的指针。 short int a[10], *p = a; 注意:p + n会根据p的类型计算内存地址,即p+n*sizeof(类型)。如p的当前值为2000,则p+1为2000+1*2=2002,而不是2001。
指向数组的指针 p仍然指向数组首地址 char str[10], *p; p = str; for (int k = 0; k < 10; k++) p[k] = 'A' + k;//也可写成*(p+k) = 'A' + k p指向数组元素str[9]的下一内存单元 char str[10], *p; p = str; for (int k = 0; k < 10; k++) *p++ = 'A' + k;//相当于 *p = 'A' + k; p++; 注意:数组名是地址常量,切不可对其赋值,也不可做++或--运算。例如:int a[10];如果在程序中出现a++或a--则是错误的。
a 0 5 1 8 p p 2 7 3 6 4 2 5 7 6 3 例: 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) () 6 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]); } 输出结果: 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 注意指针的当前值 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); } p=a; 指针变量可以指到数组后的内存单元
*(a[0]+1) 2000 2000 a *(*(a+0)+1) a[0] a[0][0] 2002 a[0]+1 a[0][1] *(a+0)+1 a[0][2] a[0][3] 2008 2008 a+1 a[1] a[1][0] 2010 a[1]+1 a[1][1] *(a+1)+1 a[1][2] a[1][3] 2016 a+2 2016 a[2] a[2][0] 2018 a[2]+1 a[2][1] *(a+2)+1 a[2][2] a[2][3] 二维数组的理解 列指针 行指针 元素 int a[3][4];
2000 a a[0] a[0][0] 2000 2002 a[0][1] a[0][2] a[0][3] 2008 a+1 a[1] a[1][0] 2008 2010 a[1][1] a[1][2] a[1][3] a+2 2016 a[2] 2016 a[2][0] 2018 a[2][1] a[2][2] a[2][3] 二维数组的理解 int a[3][4]; • a+i-----第i行的首地址 • a[i]+j *(a+i)+j -----第i行第j列的元素地址 • a+i与a[i]的值相等,但含义不同 • a+i指向行,是行指针 • a[i]指向列,是列指针
指向二维数组的指针 • 初始化格式: 数据类型符 (*指针变量名)[常量表达式]; 括号不能省 二维数组第二维的大小 例:int a[2][3]; int (*p)[3]; () int *p[3]; () int (*p)[2]; ()
p = a p 2000 2000 a[0][0] 1 a[0](2000) 2002 a[1] a[0] a[0][1]2 a[1](2006) 2004 a[0][2] 3 2006 a[1][0] 4 2008 a[1][1]5 2010 a[1][2]6 p++后p的指向 指向二维数组的指针 #include <stdio.h> int main ( ) { short a[2][3] = {{1, 2, 3},{4, 5, 6}}; short (*p)[3]; short i, j; p = a; printf("%d ", p[0][1]); p++; printf("%d", p[0][1]); } 看成一维数组 运行结果: 2 5 4 5 6
p[0]2000 p[1]2001 指针数组p p[2]2002 c[0]'a' 2000 数组c 2001 c[1]'b' 2002 c[2]'c' 指针数组 当某个数组元素都是指针型数据时,称为指针数组。 数据类型符 *变量名[常量表达式]; 例: char c[3] = {'a', 'b', 'c'}; char *p[3]; p[0] = &c[0]; p[1] = &c[1]; p[2] = &c[2];
数组指针 指针数组 int (* p)[3]; int* p[3]; p是指针变量 p是数组变量 p中的单元都是指针 p指向列数为3的数组 p中的单元都是int型的指针 p指向的数组是int型数组 指针数组与数组指针的比较
src src src src src src 指针与字符串 p p p p p p E L L O \0 H dst #include <stdio.h> char *strcpy(char* dst, char* src){ char *p = dst; while(*dst) *p++ = *src++; *p = '\0'; return dst; } int main(){ char str[10]; strcpy(str, "HELLO"); puts(str); } 使用指针实现字符串拷贝函数
野指针 如果一个指针没有指向一个有效内存就被引用,则被称为“野指针”操作或空指针赋值。野指针操作尽管编译时不会出错,但很容易引起程序运行时表现异常,甚至导致系统崩溃。 char *pstr; char str[8]; scanf ("%s",pstr); //野指针操作,pstr没有指向有效内存 strcpy (pstr, "hello"); //野指针操作 pstr = str; //pstr指向数组str所对应内存单元的首地址 strcpy (pstr, "0123456789"); //不是野指针,但会造成数组越界
随机值,有可能指向系统程序区 0001 系统程序区 假设首地址为0001 此时对pstr指向的内存单元赋值 野指针危害 指针pstr所占内存 pstr 0001 char *pstr; *pstr = ‘a’;
内存分配 当定义变量或数组时,系统会按照其数据类型及大小来分配相应的内存单元,这种内存分配方式称为静态内存分配 。 short k; //2个字节 char ch[10]; //10个字节 int n; scanf ("%d", &n); int a[n]; 错误!如果在程序运行过程中决定大小,需要动态内存分配
动态内存分配 动态内存分配是指在程序运行过程中,根据程序的实际需要来分配一块大小合适的连续的内存单元。 void *malloc( unsigned int size ); • size:是分配的内存的大小(以字节为单位)。 • 返回值: • 失败,则返回值是NULL(空指针)。 成功,则返回值void*空类型指针,使用时需要转型 int n; char *p = (char*)malloc(n*sizeof(char)); //p就指向了一个有n个元素的字符数组 for(int i=0;i<n;i++) scanf(“%c”, &p[i]);
动态内存释放 • 内存是宝贵的资源,所有程序共享使用,因此需要及时释放 • 静态分配的内存根据生存周期自动释放 • malloc动态分配的内存,需要手工释放 void free (void *block); char *p = (char*)malloc(…) free( p);
p2(2级) a(整型) p1(1级) &p1 3 &a 多级指针 • 当指针p1指向的普通变量时,p1称为一级指针 • 当指针p2指向的变量是指针变量p1时,p2便成了二级指针 • 当指针p3指向的变量是指针变量p2时,p3便成了三级指针 • …… 一级指针 目标变量 二级指针 int a = 3; int *p1; int **p2; P1 = &a; P2 = &p1; **p2 = 5; 5 *p2是p1指针,**p2是a变量
变量b 形参a 0 0 5 指针p 变量b &b 0 &b void func (int a) { a = 5; } void main ( ) { int b = 0; func (b); printf ("b = %d\n", b); } void func (int *p) { *p = 5; } void main ( ) { int b = 0; func (&b); printf ("b = %d\n", b); } 传值调用 传址调用 5 指针作为函数的参数 指针作为参数时,传递的是地址,属于双向传递 运行结果:b = 5 运行结果:b = 0
…... (main) 2000 2002 变量a 2004 (swap) 2006 5 变量b 变量y 变量x 变量t 2008 9 200A …... 交换两个变量 void swap (int x, int y) { int t = x; x = y; y = t; } int main ( ) { int a=5, b=9; swap(a,b); printf("%d,%d\n",a,b); } 5 9 COPY 9 5 5
…... (main) 2000 2002 整型变量a 指针p2 指针p1 整型p 2004 指针p_2 指针p_1 2006 2000 整型变量b 2008 2002 (swap) 200A 200C 200E ... 2010 交换两个变量 void swap(int *p1, int *p2) { int p; p = *p1; *p1 = *p2; *p2 = p; } int main ( ) { int a=5, b=9; swap(&a,&b); printf("%d,%d\n",a,b); } 9 5 5 9 2000 2002 COPY 5
TOJ 3106 贫富差距 • 题目简介: • 有n个人(最多10000),每个人有m种财富,m最大达到100万,需要按照财富总和从大到小排序,除了财富为0的之外,按顺序输出。 • 问题分析: • 如果用二维数组,则有10000行1000000列,需要内存:4*10^10=40GB,内存不够用 • 事实上,100万财富的人数很少(可能只有1个人) • 解决方法: • 使用指针数组动态分配大约只要4*10^6=4M。
题目分析 定义指针数组 int* p[10000]; p[i]的前三个元素p[i][0], p[i][1], p[i][2]分别存储财富数目、财富总和、编号。
核心代码1 int* a[10000]; for(i=0;i<n;i++)//初始化 { scanf("%d", &m); a[i] = (int*)malloc((m+3)*sizeof(int)); int t = 0; for(j=0;j<m;j++){ scanf("%d", &a[i][j+3]); t += a[i][j+3]; } a[i][0] = m; a[i][1] = t; a[i][2] = i+1; }
核心代码2 for(i=0;i<n-1;i++)//冒泡排序 { for(j=0;j<n-i-1;j++) { if(a[j][1]<a[j+1][1] || a[j][1]==a[j+1][1]&&a[j][2]>a[j+1][2]) {//交换 int *t; t = a[j]; a[j] = a[j+1]; a[j+1] = t; } } }
max 指令1 函数max所占内存单元 指令2 指令n 函数指针 • 定义格式 函数类型(*指针变量)( [形参类型1, 形参类型2,…, 形参类型n] ) 函数在编译时被分配的入口地址,用函数名表示 函数指针= [ & ]函数名; int max (int a, int b) { return (a > b? a : b); } int (*p)(int, int);//函数指针p p = max;