目录
前言
C语言中,指针(即指针变量)用来存放变量的地址
int a = 10;
int* pa = &a; //pa是指针变量,用来存放整型变量a的地址
二级指针
int a = 10; //整型变量a
int* pa = &a; //一级指针变量pa,存放a的地址
int* *ppa = &pa; //二级指针变量ppa,存放pa的地址
如何访问a呢?试试运行如下代码
int a = 10; //整型变量a
int* pa = &a; //一级指针变量pa,存放a的地址
int* *ppa = &pa; //二级指针变量ppa,存放pa的地址
printf("%d\n", *pa);
printf("%d\n", **ppa);
运行结果如下:
具体分析一下指针类型
//创建了一个变量a,类型为整型
int a = 10;
//创建了一个变量pa,“*”代表变量pa是一个指针变量,“int”代表pa指向的变量是一个整型变量
int* pa = &a;
//创建了一个变量ppa,“*”代表变量ppa是一个指针变量,“int*”代表ppa指向的变量是一个“int*”类型的变量
//int*类型变量可以理解为一级指针变量
int* * ppa = &pa;
//这里的解释对之后理解指针的其他用法有很大帮助
字符指针
//单个字符的访问与修改
char ch = 'a';
char* c1 = &ch;
printf("%c\n", *c1);
*c1 = 'b';
printf("%c\n", *c1);
//字符串的访问
char* p = "abcdefg";
printf("%s", p);
//指针变量p存放了字符串首元素的地址,当printf使用%s打印时给出首元素地址,会按照顺序打印整个字符串
//遇到'\0'停止
需要注意的一点是,我们不可以修改上述代码中字符串的内容
char* p = "abcdefg";
//当这样写时,“”内的内容是常量字符串
//正确稳妥的写法如下
const char* p = "abcdefg";
//const放在最前面,修饰*p,则*p不会被改变
指针数组
首先要明确的是:指针数组是数组而非指针,指针数组是存放指针变量的数组
我们在学习数组之前,要想创建多个变量(这里以3个变量为例) ,我们的做法是:
int a;
int b;
int c;
学习完数组后:
int arr[3]; //“[]”代表arr是数组,“int”代表数组内的元素为整型
那么对于指针和指针数组也是如此:
int a;
int b;
int c;
int* pa = &a;
int* pb = &b;
int* pc = &c;
//指针数组
int* p[3] = { &a, &b, &c }; //“[]”代表p是数组,“int*”代表数组内的元素是“int*”类型
//(即一级整型指针)
//此时p就是存放指针的数组,即指针数组
按照上面的理解看一下这句代码
char** arr[4];
//"[]"说明arr是一个数组,“char**”代表arr中的元素是“char**”类型(即二级字符指针)
指针数组可以用来模拟二维数组,如下代码所示:
int row = 0; //行
int col = 0; //列
//二维数组
int arr[3][4] = { { 1, 2, 3, 4 }, { 2, 3, 4, 5 }, { 3, 4, 5, 6 } };
for(row = 0; row < 3; row++)
{
for(col = 0; col < 4; col++)
{
printf("%d ", arr[row][col]);
}
printf("\n");
}
//指针数组
int arr1[4] = { 1, 2, 3, 4 };
int arr2[4] = { 2, 3, 4, 5 };
int arr3[4] = { 3, 4, 5, 6 };
int* arr[3] = { arr1, arr2, arr3 }; //数组名是数组首元素地址
for(row = 0; row < 3; row++)
{
for(col = 0; col < 4; col++)
{
printf("%d ", arr[row][col]);
//换成这样写试试:printf("%d ", *(arr[row] + col));
}
printf("\n");
}
运行结果都是一样的, 在这里就不附上了
由上图最后一条注释可知: arr[ i ]与*(arr + i)是等价的
上面的两种代码,区别在于:
二维数组元素在内存中是连续存放的,但指针数组并不一定
数组指针
我们知道:
整型指针是指向整型的指针
字符指针是指向字符的指针
那么数组指针就是指向数组的指针
int* arr1[10];
int (*arr2)[10];
//arr1和arr2分别是什么?
arr1先与[10]结合,说明arr1是一个数组,剩下的int*是数组元素类型,所以arr1是指针数组
arr2先与*结合,说明arr2是一个指针,int [10]代表arr2指向的对象是一个有10个整型元素的数组([10]代表数组,int代表数组元素是整型)
即arr2是一个数组指针,可以指向一个数组,该数组有10个元素,每个元素是int类型
分别把数组名去掉就能看出类型
int* [10] 指针数组
int (*)[10] 数组指针
一般情况下,数组名代表的是数组首元素地址,而&数组名代表整个数组的地址
那么如何使用数组指针来存放数组的地址呢?
int arr[10] = { 0 };
//数组指针是一个指针
*p
//指针指向数组
*p[]
//这样写p会先与[]结合,变成指针数组,所以应该这样写
(*p)[]
//指向的数组有10个元素
(*p)[10]
//每个元素的类型是整型
int (*p)[10] = &arr;
接下来做一个小练习:
char* arr[5];
pc = &arr;//此处pc应该怎么定义类型
答案如下:
char* arr[5];
//基于前面的讲解,这里直接告诉你这是一个指针数组
(*pc)[5] = &arr;
//&arr是整个数组的地址,所以需要一个指向数组的指针来接收
char* (*pc)[5] = &arr;
//由于指针数组中的元素是char*类型,所以在前面补上char*,意指arr中的元素类型
数组指针的使用常用于二维数组,虽然也能用于一维数组,但会增添不必要的麻烦
int arr1[10]={ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int (*p)[10] = &arr1;
int i = 0;
for(i = 0; i < 10 ; i++)
{
printf("%d ", *(*p + i));
}
//*(*p + i)我们一步步来理解
//首先p中存放了arr1整个数组的地址,*p代表取出了arr1数组首元素的地址
//*p + i 即向数组后面的元素移动
//但此时*p + i 仍是地址,想访问其中的元素仍需要解引用,即*(*p + i)
//这段代码能打印数组所有元素的值,但是我们平常肯定不会这么写
我们平常一定会这么写:
int arr1[10]={ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int* p = arr1;
int i = 0;
for(i = 0; i < 10 ; i++)
{
printf("%d ", *(p + i));
}
//简单方便又好理解
//数组指针用在二维数组比较好用
那么来看看数组指针怎么用到二维数组中去:
还是以打印整个数组元素为例,我们写成函数的形式
//运用以前的知识,我们能写出这样的代码
void print(int arr1[][5], int row, int col);
int main()
{
int arr[3][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
int row0 = sizeof(arr) / sizeof(arr[0]); //利用数组名的特点计算行与列
int col0 = sizeof(arr[0]) / sizeof(arr[0][0]);
print(arr, row0, col0);
return 0;
}
void print(int arr1[][5], int row, int col)
{
int i = 0;
for(i = 0; i < row; i++)
{
int j = 0;
for(j = 0; j < col; j++)
{
printf("%d ", arr1[i][j]);
}
printf("\n");
}
}
那我们如果用数组指针来作为形参如何呢?
//运用以前的知识,我们能写出这样的代码
void print(int (*p)[5], int row, int col);
int main()
{
int arr[3][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
int row0 = sizeof(arr) / sizeof(arr[0]); //利用数组名的特点计算行与列
int col0 = sizeof(arr[0]) / sizeof(arr[0][0]);
print(arr, row0, col0);
return 0;
}
void print(int (*p)[5], int row, int col)
{
int i = 0;
for(i = 0; i < row; i++)
{
int j = 0;
for(j = 0; j < col; j++)
{
printf("%d ", *(*(p + i) + j));
}
printf("\n");
}
}
//arr是一个3行5列的二维数组,print(arr)实际上是把3个有5个元素的数组的首地址传了过去
//(即第一个有5个元素的数组的地址),详解请看之前本人账号数组的讲解或自己查阅“数组名的含义”
我们来讲解一下 void print(int (*p)[5], int row, int col)和 *(*(p + i) + j)的含义
arr传递的是首元素的地址,arr是二维数组,所以arr实际传递的是第一行整个数组的地址
所以我们需要一个指向数组的指针来接收,数组中的每个元素又是整型,这就有了
int (*p)[5]
而*(p + i)取出的是第i + 1行数组中的首元素地址,想要具体访问第j + 1列元素的数值就要这样 *(*(p + i) + j)
由上面的讲解可知,p + 1实际上跳过了一行的元素,即20个字节,这是为什么呢?
有一种理解是这样的:
经过前面的学习我们知道
int* pp;
//*代表pp是一个指针变量,int代表pp指向的对象是整型
int (*p)[5];
//我们想知道为什么加1会跳过的字节数,就要先知道p是什么类型的变量
int (*)[5]
//将变量名去掉就可以得到该变量的类型
// int (*)[5],*代表p为指针变量,int [5]代表p指向一个有5个元素的数组
//所以在这里加一会跳过整个数组,即20个字节
在进行下一个内容的学习之前,我们先来判断如下变量的类型,作为以上内容的一个小练习
int arr1[5];
int* arr2[5];
int (*arr3)[5];
int (*arr4[10])[5];
arr1先与[5]结合,说明arr1是一个有5个元素的数组,int代表数组内的元素(即这5个元素)为整型
arr2先与[5]结合,说明arr2是一个有5个元素的数组,int*代表数组内的元素为整型指针
*说明arr3是一个指针变量,[5]说明arr3指向的对象是一个含有5个元素的数组,int代表数组内的元素为整型
arr4先与[10]结合,说明arr4是一个含有10个元素的数组,然后把arr4[10]去掉,剩下来的即为数组元素的类型,即int (*)[5],说明数组内的元素为数组指针
综上:
arr1为数组
arr2为指针数组
arr3为数组指针
arr4为存放数组指针的数组
数组参数和指针参数
写代码时经常要把数组或者指针传给函数,这里我们来总结一下传参的方式
一维数组传参
int main()
{
int arr1[10] = { 0 };
test1(arr1);
int* arr2[10];
test2(arr2);
}
//以下为传参方式
void test1(int arr[]);
void test1(int arr[10]);
void test1(int* p);
void test2(int* arr[10]);
void test2(int** arr); //二维指针存放的是一维指针的地址
二维数组传参
int main()
{
int arr0[3][5] = { 0 };
test(arr0);
return 0;
}
void test(int arr[3][5]);
void test(int arr[][5]);
void test(int (*p)[5]);
一级指针传参
一级指针没什么特殊的,传过去的指针是什么类型,就用什么类型的指针来接收就OK
比如:
void print(int *p, int sz);
int main()
{
int arr[5] = { 1, 2, 3, 4, 5 };
int size = sizeof(arr) / sizeof(arr[0]);
int* a = arr;
print(a, size);
return 0;
}
void print(int *p, int sz)
{
int i = 0;
for(i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
}
}
二级指针传参
void print(int **p);
int main()
{
int num = 10;
int* pa = #
int* *ppa = &pa;
print(&pa);
print(ppa);
return 0;
}
void print(int **p)
{
printf("num = %d\n", **p);
}
函数指针
学习函数指针要与数组指针类比
数组指针是指向数组的指针
函数指针就是指向函数的指针
一般情况下,数组名代表数组首元素的地址,&数组名代表取出整个数组的地址
但对于函数来说,函数名和&函数名都代表函数的地址 (可以自行去编译器尝试)
那么怎样能像存放数组地址那样,来存放函数的地址呢?
int Add(int x, int y)
{
return x + y;
}
int main()
{
//想要存放地址,首先要创建一个指针变量,*pf
*pf = &Add //有没有&都一样
//法1:指针指向的对象是函数,该函数类型如何判断呢?
//判断方法为int Add(int x, int y)去掉函数名和形参后,剩余的部分就是函数类型
//即int (int , int )
//法2:指针指向的对象是函数,那么函数的参数类型是什么呢?
(*pf) (int, int) = &Add;
//函数的参数的写完了,那么函数的返回值类型是什么呢?
int (*pf) (int, int) = &Add;
return 0;
}
我们可以通过函数指针来使用函数:
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf) (int, int) = &Add;
int ret = (*pf)(3, 4);
//写成这样也可以 int ret = (pf)(3, 4);
//原因就是函数名和&函数名都代表函数的地址
printf("%d", ret);
return 0;
}
你一定会觉得这么麻烦有什么必要,但实际上函数指针在一些情况下能简化我们的代码,比如:
#include <stdio.h>
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
void menu()
{
printf("*******************\n");
printf("***1.Add***2.Sub***\n");
printf("***3.Mul***4.Div***\n");
printf("*****0.Exti********\n");
printf("*******************\n");
}
int main()
{
int input;
int a;
int b;
int ret;
do
{
menu();
scanf("%d", &input);
switch(input)
{
case 1:
printf("请输入两个操作数\n");
scanf("%d %d", &a, &b);
ret = Add(a, b);
printf("%d\n", ret);
break;
case 2:
printf("请输入两个操作数\n");
scanf("%d %d", &a, &b);
ret = Sub(a, b);
printf("%d\n", ret);
break;
case 3:
printf("请输入两个操作数\n");
scanf("%d %d", &a, &b);
ret = Mul(a, b);
printf("%d\n", ret);
break;
case 4:
printf("请输入两个操作数\n");
scanf("%d %d", &a, &b);
ret = Div(a, b);
printf("%d\n", ret);
break;
case 0:
printf("退出成功\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
这段代码实现了一个整数的加减乘除,但是switch的部分代码重复了好多,看着冗杂,函数指针可以帮助我们简化这部分的代码
在上面添加calc函数的声明
void calc(int (*pf)(int, int))
{
int x;
int y;
printf("请输入两个操作数\n");
scanf("%d %d", &x, &y);
int ret = (*pf)(x, y);
printf("%d\n", ret);
}
修改switch:
switch(input)
{
case 1:
calc(Add);
break;
case 2:
calc(Sub);
break;
case 3:
calc(Mul);
break;
case 4:
calc(Div);
break;
case 0:
printf("退出成功\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
这样switch中的代码就显得很清晰,没有了之前的冗杂多余
附上完整版:
#include <stdio.h>
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
void menu()
{
printf("*******************\n");
printf("***1.Add***2.Sub***\n");
printf("***3.Mul***4.Div***\n");
printf("*****0.Exti********\n");
printf("*******************\n");
}
void calc(int (*pf)(int, int))
{
int x;
int y;
printf("请输入两个操作数\n");
scanf("%d %d", &x, &y);
int ret = (*pf)(x, y);
printf("%d\n", ret);
}
int main()
{
int input;
int ret;
do
{
menu();
scanf("%d", &input);
switch(input)
{
case 1:
calc(Add);
break;
case 2:
calc(Sub);
break;
case 3:
calc(Mul);
break;
case 4:
calc(Div);
break;
case 0:
printf("退出成功\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
函数指针数组
我们仍然以实现两个整数的加减乘除为例,我们上面运用函数指针的方法解决了case后面代码重复的问题,那么我们再来想一想,如果我想要在后面继续添加100个计算器的功能,岂不是要写100多个case吗?所以我们不用switch,用函数指针数组来处理这种情况
函数指针数组就是存放函数指针的数组
//函数指针
int (*pf)(int, int);
//函数指针数组--存放函数指针的数组
int (*pf[5])(int, int);
//分析方法如下:
//pf先与[5]结合,说明pf是一个含有5个元素的数组
//然后把分析过的部分(即pf[5])去掉,剩下的部分就是数组元素的类型
//因此int(*)(int, int)就是数组元素的类型,是一个函数指针类型
在主函数中添加
int (*arr[5])(int, int) = { 0, Add, Sub, Mul, Div };
do
{
menu();
scanf("%d", &input);
if(input == 0)
{
printf("退出成功\n");
}
else if(input >= 1 && input <= 4)
{
calc(arr[input]);
}
else
{
printf("输入错误,请重新输入\n");
}
} while (input);
这样我们想要添加新的功能时只需要修改数组中的元素和else if中的数字即可,不需要写100多个case了
附上完整代码:
#include <stdio.h>
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
void menu()
{
printf("*******************\n");
printf("***1.Add***2.Sub***\n");
printf("***3.Mul***4.Div***\n");
printf("*****0.Exti********\n");
printf("*******************\n");
}
void calc(int (*pf)(int, int))
{
int x;
int y;
printf("请输入两个操作数\n");
scanf("%d %d", &x, &y);
int ret = (*pf)(x, y);
printf("num = %d\n", ret);
}
int main()
{
int input;
int ret;
int (*arr[5])(int, int) = { 0, Add, Sub, Mul, Div };
do
{
menu();
scanf("%d", &input);
if(input == 0)
{
printf("退出成功\n");
}
else if(input >= 1 && input <= 4)
{
calc(arr[input]);
}
else
{
printf("输入错误,请重新输入\n");
}
} while (input);
return 0;
}
指向函数指针数组的指针
这个用的极少,在这里只简单的介绍一下
//函数指针
int (*pf)(int, int);
//函数指针数组--存放函数指针的数组
int (*pf[5])(int, int);
//指向函数指针数组的指针
int (*(*ppf)[5])(int, int) = &pf;
//分析方法如下:
//ppf先与*结合,*说明ppf是一个指针变量,[5]代表ppf指向一个有5个元素的数组
//把分析过的部分(即(*ppf)[5])去掉,剩下的就是数组元素的类型
//剩余部分即int(*)(int, int),说明数组元素的类型是函数指针