文章目录
前言
在学习指针之前我们要先了解理解他的概念,数据在内存中是一块一块存放的,每一块都有一个编号,那个编号是地址,而指针是一个变量,用来存放地址。
指针的大小是固定的4/8个字节(32位平台/64位平台)
在学习指针的时候我们要了解指针是怎么运算的,我的另一篇文章有对指针运算的详细介绍,我们在这里先了解各种指针的类型及应用。
& 在C语言中是取地址的作用 * 有解引用的作用;
如果·有一个变量里面存放的是指针,那么那个变量是二级指针;
一、 字符指针
存放字符地址的指针叫字符指针
//这是对字符指针的定义
char*
//一般使用
int main()
{
char ar='w';
char* p=&ar;
*p='w';
return 0;
}
这里的p就是指针变量用来存放字符w在内存中的地址,*p就是解引用指针指向地址内的数据 *p就相当于字符w;这就是字符指针;
这里还有一种情况就是存放字符串的首地址,代码如下;
int main()
{
char* p = "hello";
printf("%s\n", p);
return 0;
}
这里p指向hello的首地址还可以这样玩
printf(p);
当然我们不经常这样写代码,但是我们要知道有这样的写法,避免看到这样的代码会认为它是错的;
还有
char arr[]="hello";
char* ar="hello";
这两种是不同的,arr[]是开辟出一块内存用来存放“hello”这个字符串,而指针ar是指向一个常量字符串;当有
char* ar2="hello";
这时ar和ar2指向的是同一个常量字符串;
可以看到输出两个存放的地址是相同的(%p是打印地址)
二、 指针数组
指针数组是一个存放指针的数组;
int* arr1[10]; //整形指针的数组,数组里面存放的是指向整形的指针
char *arr2[4]; //一级字符指针的数组,数组里面存放的是指向字符型的指针
char **arr3[5];//二级字符指针的数组,数组里面存放的是指向指针的指针
指针数组就是存放指针的数组,就像整形数组里面存放的是整数,字符数组里面存放的是字符一样,指针数组里面存放指针就是这样;
int main()
{
int a = 10;
int b = 20;
int c = 30;
int* p1 = &a;
int* p2 = &b;
int* p3 = &c;
int* arr[] = { p1,p2,p3 };
printf("%d\n", *arr[0]);
printf("%d\n", *arr[1]);
printf("%d\n", *arr[2]);
return 0;
}
三、 数组指针
基本了解
我们要知道数组指针是指针而不是数组,
整形指针是指向整形的指针 int* n;
字符指针是指向字符的指针 char* ar;
数组指针当然是指向数组的指针;
//对比一下
int *p[10];
int (*p)[10];
第一个是指针数组,存放指针;
第二个是数组指针,指的是指向的是一个大小为10个整型的数组;
解释:p先和结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。
这里要注意:[]的优先级要高于 * 号的,所以必须加上()来保证p先和结合
&数组名与数组名的区别
int a[10]
//认识 a和 &a 的区别;
数组名 a 表示数组首地址,那么&a是什么,看下面一串代码
#include <stdio.h>
int main()
{
int ar[10] = {0};
printf("%p\n", ar);
printf("%p\n", &ar);
return 0;
}
我们看到他们的数值是一样的,都表示同一个地址
但是我们要注意,他们是不同的,表现在计算方面;在看下面一串代码:
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("arr = %p\n", ar);
printf("&arr= %p\n", &ar);
printf("arr+1 = %p\n", ar+1);
printf("&arr+1= %p\n", &ar+1);
return 0;
}
这里可以看到运算结结构完全不一样
这里 &ar 表示的是数组的地址而不是数组首元素的地址,理解一下;关键在传参和运算方面;
本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型
数组的地址+1,跳过整个数组的大小,所以 &ar+1 相对于 &ar 的差值是40.
而ar+1表示的是数组首元素的地址 +1 跳过一个整形的大小,所以 ar+1 相对于 ar 的差值是四
数组指针的使用
让我们来看看数组指针怎么使用。
通常情况下我们不常见数组指针,但是我们经常见二维数组;
void print_arr2(int (*arr)[5], int row, int col)
{
int i = 0;
for(i=0; i<row; i++)
{
for(j=0; j<col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int ar[3][5] = {1,2,3,4,5,6,7,8,9,10};
print_arr1(arr, 3, 5);
//数组名ar,表示首元素的地址
//但是二维数组的首元素是二维数组的第一行
//所以这里传递的ar,其实相当于第一行的地址,是一维数组的地址
//可以数组指针来接收
print_arr2(arr, 3, 5);
return 0;
}
这里 ar+i 表示的是二维数组的第i+1行,我们要对各种指针运算了解明白,
*(ar+i)等价于ar[i]
*(ar+i)+j 等价于 &ar[i][j] ; 对他进行解引用 * ( * (ar+i)+j) 就等价于 ar[i][j];
四、 数组传参和指针传参
我们一定要知道,不管是数组传参函数指针传参,传的都是地址,在局部修改该数组会对改变原数组
数组传参
一维数组传参
#include <stdio.h>
void test(int arr[])
{}
void test(int arr[10])
{}
void test(int *arr)
{}
int main()
{
int arr[10] = {0};
test(arr);
}
void test2(int *arr[20])
{}
void test2(int **arr)
{}
int main()
{
int *arr2[20] = {0};
test2(arr2);
}
//我们有自己的理解,指针数组里面存放的是指针,他的数组名可以看做成二级指针指针;
//所以他可以有二级指针接收;
二维数组传参
void test(int arr[3][5])
{}
void test(int arr[][5])
{}
void test(int (*arr)[5])
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算
指针传参
一级指针传参
#include <stdio.h>
void print(int *p, int sz)
{
int i = 0;
for(i=0; i<sz; i++)
{
printf("%d\n", *(p+i));
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9};
int *p = arr;
int sz = sizeof(arr)/sizeof(arr[0]);
//一级指针p,传给函数
print(p, sz);
return 0;
}
当一个函数的参数部分为一级指针的时候,函数能接收一级指针,能接收一维数组名,能接收 &a;
二级指针传参
#include <stdio.h>
void test(int** ptr)
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int*p = &n;
int **pp = &p;
test(pp);
test(&p);
return 0;
}
当函数的参数为二级指针的时候,可以接收什么:
void test(char **p)
{
}
int main()
{
char c = 'b';
char*pc = &c;
char**ppc = &pc;
char* arr[10];
test(&pc);
test(ppc);
test(arr);
return 0;
}
五、 指针函数和函数指针
指针函数
指针函数就是一个函数,表示一个返回指针的函数;
例如:
int test(int x,int y);//一个普通的函数声明
int *test(int x,int y);//这是指针函数
这和上面那个函数唯一的区别就是在函数名前面多了一个*号,而这个函数就是一个指针函数。其返回值是一个 int 类型的指针,是一个地址。
函数指针
函数指针,其本质是一个指针变量,该指针指向这个函数。总结来说,函数指针就是指向函数的指针。
首先看一段代码:
#include <stdio.h>
void test()
{
printf("hehe\n");
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
这里 函数名 和 &函数名 都代表函数地址,我们知道了函数的地址,那么我们用什么来接收函数的地址呢;我们来看下面:
void test()
{
printf("hehe\n");
}
void (*pfun1)();
void *pfun2();
pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参
数,返回值类型为void。
下面我来演示使用方式:
#include <stdio.h>
int test(int a,int b)
{
int c = a + b;
return c;
}
int main()
{
int i = 9;
int j = 9;
int (*fp)(int, int) = test;
int c = fp(i, j);
printf("%d", c);
return 0;
}
当要调用函数时,定义一个pf的函数指针指向函数。那么int ret=(*pf)(2,3)和int ret=pf (2,3)等价。
六、 函数指针数组
数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组,
比如:
int *arr[10];
那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?
int (*parr1[10])();
parr1 先和 [] 结合,说明 parr1是数组,是 int (*)() 类型的函数指针。
让我们看看他怎么使用
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a*b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div };
while (input)
{
printf( "*************************\n" );
printf( " 1:add 2:sub \n" );
printf( " 3:mul 4:div \n" );
printf( "*************************\n" );
printf( "请选择:" );
scanf( "%d", &input);
if ((input <= 4 && input >= 1))
{
printf( "输入操作数:" );
scanf( "%d %d", &x, &y);
ret = (*p[input])(x, y);
}
else
printf( "输入有误\n" );
printf( "ret = %d\n", ret);
}
return 0;
}
七、回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
总的来说就是把一个函数像参数一样给另一个函数使用,另一个函数根据参数的不同可以使用不同的函数;
让我们来看下面代码:
#include <stdio.h>
int add(int a,int b)
{
return a+b;
}
int shear(int a, int b)
{
return a - b;
}
int multiply(int a, int b)
{
return a * b;
}
int divide(int a, int b)
{
return a / b;
}
int run(int i,int j, int (*pf)(int ,int))
{
return pf(i, j);
}
int main()
{
int i = 9;
int j = 9;
int c = run(i, j, add);
int d = run(i,j, divide);
printf("%d", c);
printf("%d", d);
return 0;
}
可以用同一个函数调用不同是函数用来实现对两个数的不同操作,这样在你像实现不同运算的时候只需要修改参数就行,这在我这里看不出来优势,如果run(就是调用函数)是一个很复杂的函数,你想要用普通的函数调用实现对数的加减乘除就会是很麻烦的问题,但是如果传参就不一样了,只需要修改一个主函数的参数就行;
在回调中,主程序把回调函数像参数一样传入调用函数, 这样一来,只要我们改变传进调用函数的参数,就可以实现不同的功能,这样就变得很灵活,并且当调用函数很复杂或者不可见的时候利用回调函数就显得十分方便,增加了代码的可读性和观感。