善于利用指针<持续更新>
绪论
在本篇文章中作者将结合自己所学,为大家带来一篇详细的关于C语言指针的讲解,因为内容较多,所以我会每天更新,还请期待。本篇文章参考书籍:谭浩强编著的那本
什么是指针?
如果在程序中定义了一个变量,在对程序进行编译时,系统就会给这个变量分配内存单元。编译系统根据程序中定义的变量类型,分配一定长度的空间。内存区的每一个字节有一个编号,这就是“地址”。
由于通过地址能找到所需的变量单元,可以说, 地址指向该变量单元,将地址形象化地称为“指针”。
指针变量
指针变量的定义
我们可以用下面的代码来命名一个新的指针:
int *pointer_1;
char *pointer_2;
其中int或char是指针所指向的变量类型,pointer_1或pointer_2表示变量名。即:
指针变量的引用
我们可以这样操作指针变量
int *p; //定义一个指针p,指向整型变量
int a=20;
p=&a;//p指向整型变量a的地址
printf("%d %d %p %p",*p,a,p,&a);
& 它称为取地址符,用于获取一个变量的地址。
%p 用于打印地址
代码的运行结果为:
从运行结果不难看出p指向a的地址,而*p指向a的值
指针与数组
通过指针引用一维数组
int *p;
int array[10] = {0,1,2,3,4,5,6,7,8,9};
p = &array[0];
在上述代码中,我们使指针p指向了数组中的第一个元素的地址,我们称此种方法为下标法,除此之外,我们还需要知道数组名就是数组首元素的地址,因此上述代码可以写为
int *p;
int array[10] = {0,1,2,3,4,5,6,7,8,9};
p = array;
在指针已指向一个数组元素时,可以对指针进行以下运算:
●加一个整数(用+或+=),如p+1,表示指向同一数组中的下一个元素;
●减一个整数(用-或-=),如p-1,表示指向同一数组中的.上一个元素;
●自加运算,如p++,++p;
●自减运算,如p–,--p。
我们可以通过下图清楚的看到指针与数组的关系
输入与输出
在未学习指针前,我们通常这样输入数组数据
for(int i=0;i<arraySize;i++) //arraySize即数组大小
scanf("%d",&array[i]);
在学习指针后,我们可以利用指针来输入数据
for(p=array;p<(array+arraySize);p++)
scanf("%d", p);
输出数据
for(p=array;p<(array+arraySize);p++)
printf("%d", *p);
运行结果为
利用数组引用数组元素是还有以下技巧:
我们以p=a为例
例1、
p++; //使指针指向a[1]
*p; //获取a[1]的值
例2、
*(p++); //先获取*p的值,再让p加一
*(++p); //先让p加一,在获取*p的值
例3、
*p++; /*由于++和*同优先级,结合方向自右而左,因此它等价于*(p++)。先引用p的值,实现*p的运算,然后再使p自增1*/
++(*p); //先获取*p的值,再加一
用数组名作参数
例如:
printf("%d",*array);
scanf("%d",(array+i));
指向一维数组的指针
int (*p)[4];
表示p指向由4个整型变量组成的一维数组。我们可以这样来操作
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a[4] = { 1,3,5,7 }; //定义一维数组a,包含4个元素
int(*p)[4]; //定义指向包含4个元素的一维数组的指针变量中
p = &a; //使p指向一维数组
printf("%d\n", (*p)[3]); //输出a[3],输出整数7
system("pause");
return 0;
}
运行结果
通过指针引用多维数组
我们以下图为例
我们通过下列程序来更直观地体现
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a[3][4] = { 1,3,5,7,9,11,13,15,17,19,21,23 };
printf("%p %p\n", a, *a); //0行起始地址和0行0列元素地址
printf("%p %p\n", a[0], *(a + 0)); //0行0列元素地址
printf("%p %p\n", &a[0], &a[0][0]); //0行起始地址和0行0列元素地址
printf("%p %p\n", a[1], a + 1); //1行0列元素地址和1行起始地址
printf("%p %p\n", &a[1][0], *(a + 1) + 0); //1行0列元素地址
printf("%p %p\n", a[2], *(a + 2)); //2行0列元素地址
printf("%p %p\n", &a[2], a + 2); //2行起始地址
printf("%d %d\n", a[1][0], *(*(a + 1) + 0)); //1行0列元素的值
printf("%d %d\n", *a[2], *(*(a + 2) + 0)); //2行0列元素的值
printf("%d %d\n", a[2][2], *(*(a + 2) + 2)); //2行2列元素的值
system("pause");
return 0;
}
运行结果
通过指针引用字符串
先拿程序来举个例子
#include <stdio.h>
int main()
{
char string[20] = { "I love China!" };
char *p;
p = string;
printf("%s\n", p); //%s可以对字符串进行整体输入与输出
printf("%s\n", p + 7);
printf("%c", *(p + 9)); //输出字符串中某个字母
return 0;
}
运行结果
指向函数的指针
定义指向函数的指针
int (*p)(int,int);
定义p是一个指向函数的指针,它可以指向函数类型为整型且有两个整形参数的函数。此时,指针变量p的类型用int (*)(int,int),即:
用函数指针调用函数
#include <stdio.h>
int max(int, int);
int main()
{
int(*p)(int, int); //定义指向函数的指针变量p
int a, b, c;
p = max; //使p指向max函数
printf("please enter a and b:");
scanf_s("%d,%d", &a, &b);
c = (*p)(a, b); //通过指针变量调用max函数
printf("a=%d\nb=%d\nmax=%d\n", a, b, c);
return 0;
}
int max(int x, int y)
{
int z;
if (x > y)
z = x;
else
z = y;
return z;
}
运行结果
指向函数的指针变量的一个重要用途是把函数的入口地址作为参数传递到其他函数。
例如我们定义一个函数fun
//实参函数名 f1 f2
void fun(int(*x1)(int), int(*x2) (int, int)) //定义fun函数,形参是指向函数的指针变量
{
int a, b, i = 3, j = 5;
a = (*x1)(i); //调用f1函数,i是实参
b = (*x2)(i, j); //调用f2函数,i,j是实参
}
这样,我们就可以同时调用f1和f2函数
函数指针使用时的注意事项
(1) 定义指向函数的指针变量,并不意味着这个指针变量可以指向任何函数,它只能指向在定义时指定的类型的函数。
(2) 如果要用指针调用函数,必须先使指针变量指向该函数。
(3) 在给函数指针变量赋值时,只须给出函数名而不必给出参数。
(4) 用函数指针变量调用函数时,只须将(*p)代替函数名即可(p为指针变量名),在(*p)之后的括号中根据需要写上实参。
(5) 对指向函数的指针变量不能进行算术运算,如p + n, p++, p–等运算是无意义的。
(6) 用函数名调用函数,只能调用所指定的一个函数,而通过指针变量调用函数比较灵活,可以根据不同情况先后调用不同的函数。
返回指针值的函数
定义返回指针值的函数
int *a(int x,int y);
注意:
在“*a”两侧没有括号,在a的两侧分别为*运算符和()运算符。而()优先级高于*,因此a先与()结合,显然这是函数形式。这个函数前面有一个*,表示此函数是指针型函数(函数值是指针)。最前面的int表示返回的指针指向整型变量。
a是函数名,调用它以后能得到一个int*型(指向整型数据)的指针,即整型数据的地址。x和y是函数a的形参,为整型。
定义返回指针值的函数一般形式为:
使用返回指针值的函数
例题:有a个学生,每个学生有b门课程的成绩。要求在用户输入学生序号以后,能输出该学生的全部成绩。用指针函数来实现。
#include <stdio.h>
float *search(float(*pointer)[4], int n);
int main()
{
float score[][4] = { {60,70,80,90},{56,89,67,88},{34,78,90,66} };
float *p;
int i, k;
printf("enter the number of student:");
scanf("%d", &k); //输入要找的学生的序号
printf("The scores of No.%d are:\n", k);
p = search(score, k); //调用search函数,返回score[k][0]的地址
for (i = 0; i < 4; i++)
printf("%5.2f\t", *(p + i)); //输出score[k][0]~score[k][3]的值
printf("\n");
return 0;
}
float *search(float(*pointer)[4], int n) //形参pointer是指向一维数组的指针变量
{
float *pt;
pt = *(pointer + n); //pt的值是&score[k][0]
return(pt);
}
指针数组和多重指针
定义一维指针数组
int *p[4];
定义指针数组一般形式为:
动态内存分配与指向它的指针变量
什么是内存的动态分配
全局变量是分配在内存中的静态存储区的,非静态的局部变量(包括形参)是分配在内存中的动态存储区的,这个存储区是一个称为栈(stack)的区域。除此以外,C语言还允许建立内存动态分配区域,以存放一些临时用的数据,这些数据不必在程序的声明部分定义,也不必等到函数结束时才释放,而是需要时随时开辟,不需要时随时释放。这些数据是临时存放在一个特别的自由存储区,称为堆(heap)区。可以根据需要,向系统申请所需大小的空间。由于未在声明部分定义它们为变量或数组,因此不能通过变量名或数组名去引用这些数据,只能通过指针来引用。
怎样建立内存的动态分配
用malloc函数开辟动态存储区
函数原型
作用是在内存的动态存储区中分配一个长度为size的连续空间。形参size的类型定为无符号整型(不允许为负数)。此函数的值(即“返回值”)是所分配区域的第一个字节的地址,或者说,此函数是一个指针型函数,返回的指针指向该分配域的第一个字节。
函数使用
malloc(100); //开辟100字节的临时分配域,函数值为其第一个字节的地址。
指针的基类型为void,即不指向任何类型的数据,只提供一个纯地址。如果此函数未能成功地执行(例如内存空间不足),则返回空指针(NULL)。
用calloc函数开辟动态存储区
函数原型
作用是在内存的动态存储区中分配n个长度为size的连续空间,这个空间一般比较大,足以保存一个数组。
函数使用
p=calloc(50,4); //开辟50*4字节的临时分配域,把首地址赋给指针变量p。
用calloc函数可以为一维数组开辟动态存储空间,n为数组元素个数,每个元素长度为size。这就是动态数组。函数返回指向所分配域的第一个字节的指针;如果分配不成功,返回NULL。
用realloc函数重新分配动态存储区
函数原型
如果已经通过malloc函数或calloc函数获得了动态空间,想改变其大小,可以用realloc函数重新分配。
函数使用
realloc(p,100); //将p所指向的已分配的动态空间改为100字节
用realloc函数将p所指向的动态空间的大小改变为size。p的值不变。如果重分配不成功,返回NULL。
用free函数释放动态存储区
函数原型
作用是释放指针变量p所指向的动态空间,使这部分空间能重新被其他变量使用。p应是最近一次调用calloc或malloc函数时得到的函数返回值。
函数使用
free(p); //释放p所指向的已分配的动态空间
free函数无返回值
注意事项
重点!!!!
以上4个函数的声明在stdlib.h头文件中,在用到这些函数时应当用“#include <stdlib.h>”指令把stdlib.h头文件包含到程序文件中。
指针变量的归纳比较
指针的优点
1.提高程序效率;
2.在调用函数时当指针指向的变量的值改变时,这些值能够为主调函数使用,即可以从函数调用得到多个可改变的值;
3.可以实现动态存储分配。
如果使用指针不当,会出现隐蔽的、难以发现和排除的故障。因此,使用指针要十分小心谨慎。
后记
希望本篇文章对大家学习指针有所帮助,如果对文章中的内容有所疑问,欢迎大家留言,如果内容需要更新,作者也会及时更新。
2019年6月25日