580 likes | 676 Views
第九讲 指针. 合肥师范学院计算机科学与技术系. 关于指针的原则. 学习原则 一定要学会 其实通常的应用很简单 与使用变量几乎无异 使用原则 永远要清楚每个指针指向了哪里 永远要清楚指针指向的位置是什么. 1000 20 x. 1004 1 y. 1008 155 z. 2000 1000 p. 2002. 1.14.1 指针的基本概念. 内存单元 地址 内容 变量名.
E N D
第九讲 指针 合肥师范学院计算机科学与技术系
关于指针的原则 • 学习原则 • 一定要学会 • 其实通常的应用很简单 • 与使用变量几乎无异 • 使用原则 • 永远要清楚每个指针指向了哪里 • 永远要清楚指针指向的位置是什么
1000 20 x 1004 1 y 1008 155 z 2000 1000 p 2002 1.14.1 指针的基本概念 内存单元 地址 内容 变量名 直接访问:通过变量名访问 int x = 20, y = 1, z = 155; printf("%d", x;) • 间接访问:通过另一个变量访问 • 把变量的地址放到另一变量中 • 使用时先找到后者 • 再从中取出前者的地址 地址 指针变量
1.14.1 指针的基本概念 • 指针也是一种数据类型 • 指针变量 • 具有指针类型的变量,专门存放地址数据的变量
如何定义指针变量? 类型名 *指针变量名 指针声明符 指针变量所指向的变量的类型 • int *p; • p 是整型指针,指向整型变量 • float *fp; • fp 是浮点型指针,指向浮点型变量 • char *cp; • cp 是字符型指针,指向字符型变量
p a &a 3 指针变量的引用 ——&与*操作符 指针变量同普通变量一样,使用之前不仅要定义说明,而且必须赋予具体的值。 1、取地址运算和间接访问运算 & 取地址运算符,给出变量的地址 int *p, a = 3; p = &a;把 a 的地址赋给 p,即 p 指向 a *p * 间接访问运算符,访问指针所指向的变量 *p:指针变量 p 所指向的变量
&用来取变量的地址 int i, *p;p = &i; int *p, a[10];p = a; int *p, a[10];p = &a[5]; *用来取指针指向地址的内容 int i, *p;p = &i;*p = 0; int *p, a[10];p = a;*p = 0; int *p, a[10];p = &a[5];*p = 0; &与*操作符
指针变量赋值—— 判断是真?是假? 应在类型相同的指针变量之间赋值 一个指针变量不能指向与其类型不同的变量! int i,*p; p=&i; int i; float *p; p=&i; int *p; float *q; p=q; int *p; p=100; 指针变量只 存放地址! 我是真的, 你猜对了吗?
*p p a &a 3 说明 int a = 3, *p; p = &a; (1) 当 p = &a 后,*p 与 a 相同 (2) int *p; 定义指针变量 p *p =10; 指针p所指向的变量,即a (3) &*p 与 &a 相同,是地址 *&a 与 a 相同,是变量 (4) (*p)++ 等价于 a++,将 p 所指向的变量值加1 *p++ 等价于 *(p++), 先取*p,然后P 自加,此时p不再指向a
指针变量与其它类型变量的对比 • 共性 • 在内存中占据一定大小的存储单元 • 先定义,后使用 • 特殊性 • 它的内容只能是地址,而不能是数据 • 必须初始化后才能使用,否则指向不确定的存储单元 • 只能指向同一基类型的变量 • 可参与的运算:加、减一个整数,自增、自减、关系、赋值
1.14.2 指针运算(1/3) • 赋值运算 • 指针在使用前一定要赋值 • 为指针变量赋的值必须是一个地址 main() { int a,*p=&a; scanf("%d",p); … } main() { int *p; scanf("%d",p); … } 错!
p-1 p p+1 指针运算(2/3) • 算术运算 • int *p, a[10]; p = a; p++; /*p的值增加多少?*/ • 指针的加减运算是以 • 其指向的类型的字长为单位的 6000 6001 6002 6003 6004 6005 6006 6007 6008 6009 6010 6011
例程2.14.2.cpp • int * pn1, * pn2; • char * pc1, * pc2; • pn1 = (int *) 100; //地址pn1为100 • pn2 = (int *) 200; //地址pn2为200 • printf("%d\n", pn2 - pn1); //输出 25, 因为(200-100)/sizeof(int) • pc1 = (char * ) pn1; //地址pc1为100 • pc2 = (char * ) pn2; //地址pc2为200 • printf("%d\n", pc1 - pc2 ); //输出 -100,因为(100-200)/sizeof(char)
指针运算(3/3) • 关系运算 • 只有指向同一种数据类型的两个指针才能进行关系运算。 • 值为1或0 • p > q p < q p == q • 指针不与非指针量进行比较,但可与NULL(即0值)进行等或不等的关系运算 • 判断p是否为空指针 • P == NULL • p != NULL
p a[0] a[1] a[2] a[3] a[0] a[1] a[2] a[3] 1.14.5指针和数组 a 6000 6001 6002 6003 6004 6005 6006 6007 a+1 • 数组名就是一个指针 • 只是不能修改这个指针的指向 • 可以定义函数的参数为数组 • 指针也可当作数组名使用 • int *p, a[10];p = a; • 数组元素的几种等价引用形式 • a[i] • *(a+i) • p[i] • *(p+i) a+2 a 6000 6001 6002 6003 6004 6005 6006 6007 p++ p++
地址 内容 数组元素 p a 3000 a[0] p+1 a+1 3002 a[1] p+i a+i a[i] p+99 a+99 3198 a[99] 指针和数组的关系 任何由数组下标来实现的操作都能用指针来完成 int a[100], *p; p = a; 或 p = &a[0]; &a[i]a[i] a+i*(a+i) p = a; sum = 0; for(i = 0; i < 100; i++) sum = sum + p[i]; 等价 p+i *(p+i) &p[i]p[i] 等价
地址 内容 数组元素 a 3000 a[0] a+1 3002 a[1] a+i a[i] a+99 3198 a[99] 用指针完成对数组的操作 int a[100], *p; 移动指针 p p p p sum = 0; for(p = a; p <= &a[99]; p++) sum = sum + *p;
输入输出数组的全部元素 方法1:下标法 方法2:指针法 main() { int a[10]; int i; for (i=0; i<10; i++) scanf("%d", &a[i]); for (i=0; i<10; i++) printf("%d ", a[i]); } main() { int a[10]; int *p, i; for (p=a; p<(a+10); p++) scanf("%d", p); for (p=a; p<(a+10); p++) printf("%d ", *p); }
例程2.14.5.1.cpp • #include <stdio.h> • int main() • { • int an[200]; • int * p ; • p = an; // p指向数组an的起始地址,亦即p指向了an[0] • * p = 10; //使得an[0] = 10 • *( p + 1 ) = 20; //使得 an[1] = 20 • p[0] = 30; // p[i] 和 *(p+i) 是等效的,此句使得an[0] = 30 • p[4] = 40; //使得 a[4] = 40 • for( int i = 0;i < 10;i ++) //通过一个循环对数组an的前10个元素进行赋值 • *( p + i) = i; • p++; // p指向 a[1] • printf("%d\n", p[0]); // 输出a[1]的值,即1。p[0] 等效于 * p • p = an + 6; // p指向a[6] • printf("%d\n", * p); // 输出 6 • return 0;
指针作为函数的参数 函数参数包括实参和形参,两者的类型要一致,可以是指针类型。 如果实参是某个变量的地址,相应的形参就是指针。 调用哪个函数,可以交换main ()中变量a和b的值? int main (void) { int a = 1, b = 2; int *pa = &a, *pb = &b; void swap1(int x, int y), swap2( int *px, int *py ), swap3 (int *px, int *py); swap1 (a, b); printf (“After calling swap1: a=%d b=%d\n”, a, b); a = 1; b = 2; swap2(pa, pb); printf (“After calling swap2: a=%d b=%d\n”, a, b); a = 1; b = 2; swap3(pa, pb); printf (“After calling swap3: a=%d b=%d\n”, a, b); return 0; }
主调函数 被调 函数 编写函数实现两数的互换 程序 2 程序 1 main() { int a, b; a = 15; b = 8; Swap( &a, &b ); printf("a=%d,b=%d",a,b); } main() { int a, b; a = 15; b = 8; Swap(a, b); printf("a=%d,b=%d",a,b); } void Swap(int x,int y) { int temp; temp = x; x = y; y = temp; } 实 参 void Swap(int *x,int *y) { int temp; temp = *x; *x = *y; *y = temp; } 形 参 Not Work!Why? 结果有何不同?
x y a b 程序 1 形 参 实 参 主调函数 被调函数 void Swap(int x, int y) { int temp; temp = x; x = y; y = temp; } main() { int a, b; a = 15; b = 8; Swap(a, b); printf("a=%d,b=%d",a,b); } y x b a 8 8 15 15
x y a b 程序 2 形 参 实 参 主调函数 被调函数 void Swap(int *x, int *y) { int temp; temp = *x; *x = *y; *y = temp; } main() { int a, b; a = 15; b = 8; Swap(&a, &b); printf("a=%d,b=%d",a,b); } a b 8 15 x y &a &b &a &b
b y a x 1 1 2 2 2 1 例 swap1() swap1 (a, b); void swap1 (int x, int y) { int t; t = x; x = y; y = t; }
b a 1 2 2 1 py px 例swap2() swap2 (&a, &b); void swap2 (int *px, int *py) { int t; t = *px; *px = *py; *py = t; } 值传递,地址未变, 但存放的变量的值改变了
b a 1 2 2 1 py px 指针作为函数参数的应用 swap3 (&a, &b); void swap3 (int *px, int *py) { int *t; t = px; px = py; py = t; } After calling swap1: a=1, b=2 After calling swap2: a=2, b=1 After calling swap3: a=1, b=2 要通过函数调用来改变主调函数中某个变量的值: (1) 在主调函数中,将该变量的地址或者指向该变量的指针作为实参 (2) 在被调函数中,用指针类型形参接受该变量的地址 (3) 在被调函数中,改变形参所指向变量的值
通过指针实现函数调用返回多个值 例输入年和天数,输出对应的年、月、日。 例如:输入2000和61,输出2000-3-1。 定义函数month_day(year, yearday, *pmonth, *pday) 用2个指针作为函数的参数,带回2个结果 int main (void) { int day, month, year, yearday; void month_day(int year,int yearday, int *pmonth,int *pday); printf(“input year and yearday: ”); scanf ("%d%d", &year, &yearday ); month_day (year, yearday, &month, &day ); printf ("%d-%d-%d \n", year, month, day ); return 0; }
day month 3 1 pday pmonth void month_day ( int year, int yearday, int * pmonth, int * pday) { int k, leap; int tab [2][13] = { {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, }; /* 建立闰年判别条件leap */ leap = (year%4 == 0 && year%100 != 0) || year%400 == 0; for ( k = 1; yearday > tab[leap][k]; k++) yearday -= tab [leap][k]; *pmonth = k; *pday = yearday; } input year and yearday: 2000 61 2000-3-1
冒泡排序算法分析 相邻两个数比较,小的调到前面,大的调到后面 9 8 8 8 8 8 5 4 4 0 8 9 5 5 5 5 4 5 0 4 5 5 9 4 4 4 6 0 5 4 4 4 9 6 6 0 6 6 6 6 6 9 0 8 0 0 0 0 0 9
i=5 i=4 i=3 i=2 i=1 9 8 8 8 8 8 5 4 4 0 8 9 5 5 5 5 4 5 0 4 5 5 9 4 4 4 6 0 5 4 4 4 9 6 6 0 6 6 6 6 6 9 0 8 0 0 0 0 0 9 j=0 to 2 j=0 to 3 j=0 to 4 j=0 to 6-1-i a[j]>a[j+1]
int main(void ) { int i, j, n, t, a[10]; n = 6; for(i = 0; i < n; i++) scanf("%d", &a[i]); for(i = 1; i < n; i++) for(j = 0; j < n-i; j++) if(a[j] > a[j+1]) { t = a[j]; a[j] = a[j+1]; a[j+1] = t; } return 0; } 9 8 5 4 6 0 i=1 j=0: 8 9 5 4 6 0 j=1: 8 5 9 4 6 0 j=2: 8 5 4 9 6 0 j=3: 8 5 4 6 9 0 j=4: 8 5 4 6 0 9
void sort(int *array, int n) { int i, j, t; for(i=1; i<n; i++) for(j=0; j<n-i; j++) if(array[j]>array[j+1]){ t = array[j]; array[j] = array[j+1]; array[j+1] = t; } } int main(void ) { int i, a[10]; for(i=0; i<10; i++) scanf("%d", &a[i]); sort(a, 10); for(i=0; i<10; i++) printf("%d ", a[i]); printf("\n"); return 0; }
a[0][0] a[0][1] a[0][2] a[1][0] a[0][0] a[0] a[0][1] a[0][2] a[1][0] a[1] a[1][1] a[1][2] a a[1][1] a[1][2] 指针与二维数组 • C语言将二维数组看作一维数组,其每个数组元素又是一个一维数组 &a[0][0] a a[0]+0 int a[2][3]; &a[0][1] a[0]+1 &a[0][2] a[0]+2 a+1 a[1]+0 &a[1][0] &a[1][1] a[1]+1 &a[1][2] a[1]+2
a[0][0] a[0][1] a[0][2] a[0][3] a[1][0] a[1][1] a[1][2] a[1][3] a[2][0] a[2][1] a[2][2] a[2][3] 行地址 列地址 a[0]+0 a[0]+1 a[0]+2 a[0]+3 a+0 &a[0] 一维数组a[0] a[0] 列地址 a+1 &a[1] a[1]+0 a[1]+1 a[1]+2 a[1]+3 a[1] 一维数组a[1] a+2 &a[2] a[2] 列地址 a[2]+0 a[2]+1 a[2]+2 a[2]+3 一维数组a 一维数组a[2] 指针与二维数组
指针与二维数组 • a • 代表二维数组的首地址,第0行的地址 • a+i • 代表第i行的地址 • *(a+i) 即 a[i] • 代表第i行第0列的地址 • *(a+i)+j 即 a[i]+j • 代表第i行第j列的地址 • *(*(a+i)+j ) 即 a[i][j] • 代表第i行第j列的元素
a[0][0] a[0][1] a[0][2] a[1][0] a[1][1] a[1][2] 指针与二维数组 二维数组指针变量说明的一般形式为: • 类型说明符 (*指针变量名)[长度] int (*p)[3]; p = a;//用行地址初始化 for (i=0; i<n; i++) for (j=0; j<m; j++) printf("%d",*(*(p+i)+j)); p p++
1.14.6字符串和字符指针 • 字符串常量 "array" "point" • 用一对双引号括起来的字符序列 • 被看做一个特殊的一维字符数组,在内存中连续存放 • 实质上是一个指向该字符串首字符的指针常量 char sa[ ] = "array"; char *sp = "point";
printf("%s ", sa+2); printf("%s ", sp+3); printf("%s\n", string"+1); char sa[ ] = "array"; char *sp = "point"; printf("%s ", sa); printf("%s ", sp); printf("%s\n", "string"); ray nt tring array point string 数组名sa、指针sp和字符串 "string" 的值都是地址
字符数组与字符指针的重要区别 char sa[ ] = "This is a string"; char *sp = "This is a string"; sa sp 如果要改变数组sa所代表的字符串,只能改变数组元素的内容 如果要改变指针sp所代表的字符串,通常直接改变指针的值,让它指向新的字符串
示例 char sa[ ] = "This is a string"; char *sp = "This is a string"; strcpy (sa, "Hello"); sp = "Hello"; sa = “Hello”; 非法 数组名是常量,不能对它赋值
字符指针-先赋值,后引用 定义字符指针后,如果没有对它赋值,指针的值不确定。 char *s ; scanf(“%s”, s); char *s, str[20]; s = str; scanf(“%s”, s); 定义指针时,先将它的初值置为空 char *s = NULL 不要引用未赋值的指针
若定义了一个指针变量,并使它指向一个字符串,就可以用下标形式引用指针变量所指的字符串中的字符。若定义了一个指针变量,并使它指向一个字符串,就可以用下标形式引用指针变量所指的字符串中的字符。 • #include <stdio.h> • voidmain() • {char*a=″I love China!″; • int i; • printf ( “ The sixth character is %c\n",a[5]); • for(i=0;a[i]!=′\0′;i++) • printf(″%c″,a[i]); • }
入口地址 指令1 指令2 指令3 … 指令n 1.14.8指向函数的指针 • 每个函数都占用一段内存单元,有一个入口地址(起始地址) • 函数名:函数的入口地址 • 函数指针:一个指针变量,接收函数的入口地址,让它指向函数 • 通过函数指针调用函数 • 做为函数的参数
函数指针的定义和赋值 int fun(x ,y) { return x > y ? x : y; } • 定义 类型名 (*变量名)( ); 所指向函数的返回值的类型 int (*funptr)( ); • 定义一个函数指针funpt • funpt指向一个返回值类型为int的函数 • 赋值 funptr = fun; • 函数fun的入口地址赋给funptr • funptr指向函数fun
通过函数指针调用函数 int fun(x ,y) { return x > y ? x : y; } int (*funptr)( ); funptr = fun; • 调用函数 • 函数名 z = fun(3, 5); • 函数指针 (*funptr)(3, 5);或funptr(3,5) (*函数指针名)(参数表)
函数指针做为函数的参数 • 实参:函数名或已赋值的函数指针 • 形参:函数指针,指向实参所代表函数的入口地址 例编写一个函数calc(f, a, b),用梯形公式求函数f(x)在[a, b]上的数值积分。 然后调用calc(f, a, b)计算下列数值积分。 分析: • 函数定义时,形参:函数指针f、积分区间上下限参数a,b • 函数调用时,实参:被积函数的名称(或函数指针)和积分区间的上下限
源程序 double f1 ( double x ) { return (x*x); } double f2 ( double x ) { return (sin(x)/x); } double calc ( double (*f)(double), double a, double b ) { double z; z = (b-a)/2 * ( (*f)(a) + (*f)(b) ); /* 调用 f 指向的函数 */ return ( z ); } int main ( void ) { double result; result = calc(f1, 0.0, 1.0); /* 函数名f1作为函数calc的实参 */ printf("1: resule=%.4f\n", result); result = calc(f2, 1.0, 2.0); /* 函数指针funp作为函数calc的实参 */ printf("2: resule=%.4f\n", result); return 0; } 1: resule=0.5000 2: resule=0.6481
指针数组的概念 char *color[5]; 类型名 *数组名[数组长度] 数组元素是指针类型,用于存放内存地址 int a[10]; • a是一个数组,它有10个元素 • 每个元素的类型都是整型 char *color[5]; • color是一个数组,它有5个元素 • 每个元素的类型都是字符指针
指针数组的概念 char *color[5] = {"red", "blue", "yellow", "green", "purple"}; • color是一个数组,它有5个元素 • 每个元素的类型都是字符指针 • 数组元素可以处理字符串 对指针数组元素的操作相当于对同类型指针变量的操作 printf("%10s %10s\n", color[i], color[j]);
例:字符串按字典顺序排序 char str[N][10] = {"Pascal","Basic","Fortran", "Java","Visual C"}; for (i=0; i<N-1; i++) { for (j = i+1; j<N; j++) { if (strcmp(str[j], str[i]) < 0) { strcpy(temp,str[i]); strcpy(str[i],str[j]); strcpy(str[j],temp); } } } 方法1:二维数组