指针进阶
本章重点:
- 字符指针
- 数组指针
- 指针数组
- 数组传参和指针传参
- 函数指针
- 函数指针数组
- 指向函数指针数组的指针
- 回调函数
在初阶的时候,我就介绍了 指针相关基础知识,在进行进阶之前,我想把几个重点再提出来,不然进阶很多东西容易混。
1、指针是用来存放地址的变量;
2、指针的大小是固定的,32位平台就是四个字节,64位平台就是8个字节;
3、指针的类型决定了指针的步长即指针在加减整数时候跳过的字节。
字符指针
字面意思 指向字符的指针变量叫字符指针,例如下面的代码,将字符‘w’,存入到 变量 ch中,ch的类型是char,取出ch的地址,存放在指针变量pc中,pc的类型是什么呢? 类比 char ch,ch的类型是char;所以指针变量pc的类型 就是 char* 。
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}
需要提醒的是如果存放常量字符串,常量字符串在内存中,是数据存储区,是不可以被修改的,所以当写下一串字符串,其实是将常量字符串的首地址存放在指针变量中的时候,最好用const修饰,保证*pa不可被修改 例如:
int main()
{
const char* pstr = "hello world.";//这里是把一个字符串首地址存放到pstr指针变量中
printf("%s\n", pstr);//从首地址往后打印,直到遇到‘\0’
return 0;
}
我们可以看一道例题,来区分,常量字符串在内存中存储首地址是什么样的
```c
#include<stdio.h>
int main()
{
char str1[] = "hello world.";
char str2[] = "hello world.";
const char* str3 = "hello world.";
const char* str4 = "hello world.";
if (str1 == str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if (str3 == str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
return 0;
程序的结果是:
这是因为,在定义数组 str1,和str2时候,内存会开辟两块不同的区间,自然会产生两个地址,但是常量字符串只会存储一份,所以 str3 和str4存储的都是 字符串首地址 h的地址。
指针数组
这个在初阶的时候介绍过,我想的再次回顾一下,为了给下面的数组指针做铺垫,首先什么整形数组?,即int arr【5】可以解释为数组arr有五个元素,每个元素都为int。类比推出 指针数组 即为存放指针的数组,存放整形int 的指针就是整形指针数组,存放字符型char的指针就是字符数组指针。可以写成 int* arr1【5】arr1优先与数组结合说明他是个数组,数组有五个元素 每个元素都为 int * 如图:
数组指针
数组指针的定义
我们还是用类比的方法,字符指针是存放字符地址的指针,它指向的内容是字符,类型为 char * ;整形指针 存放整形地址的指针,它指向的内容是整形,类型为int* ;
那数组指针 就是能够存放数组的的指针咯,那应该怎么写呢?怎么写才能指向int arr[5] 整个数组呢?首先,他是数组指针是指针 所以 要先与 * 结合 (*pa),还得能够指向数组 即**(pa)[10]**,(因为 [ ]优先级高于所以为了让pa是指针 要用括号括起来,提高其优先级,如果是整形数组就是 *int (pa)[5] = &arr;
pa的类型就是 int(*)[5],说明pa是数组指针,pa指向的内容就是 int [ 5],即 有五个元素的指针,如图:
&数组名VS数组名
我在初阶的时候,介绍过 arr只有单独是sizeof的操作数的时候,才是整个数组地址,其他全部代表首元素地址,那我在指针进阶这里 又重复提出来干啥的呢? 我们先看一段代码:
int main()
{
int arr[10] = { 0 };
printf("arr = %p\n", arr);
printf("&arr= %p\n", &arr);
printf("arr+1 = %p\n", arr+1);
printf("&arr+1= %p\n", &arr+1);
return 0;
}
我们发现,arr 和&arr的地址是一样的,不是说arr是首元素地址,&arr是真个元素地址吗?既然都一样 可不可以直接用arr代替&arr呢?我们再往下看 arr + 1的地址,和 &arr + 1的地址,FA8C和FAB0相差四十个字节,这是怎么回事,都是 + 1竟然一个增涨了四个字节,一个增长了40个字节,想想我们在指针这一章一直说的 什么决定步长,是类型哇,我们用指针变量 接受数组首元素地址 所以 它的类型就是 int* (32位四个字节)它的步长可不是一个整形吗,那整个数组的的类型是 int(*)[ 10],它自增一 就相当于 10 * sizeof(int) 即为 40个字节;所以虽然 &arr和arr值是一样的 但是它代表的意义不一样。
数组指针的使用
数组指针怎样使用呢,既然能指向整个数组,我们就举个例子来看看数组指针的使用:
#include<stdio.h>
void print(int(*pa)[5], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf("%d ", ( * (pa + i))[j]);
// *(pa + i) = pa[i] 所以 可以改写成pa[i][j];
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
print(arr, 3, 5);
return 0;
}
将数组名 arr传给函数print打印的时候,我们都知道 arr是首元素地址,但是二维数组首元素是二维数组第一行,传arr相当于传第一行的地址,即一维数组的地址,所以我们用数组指针接收:
我们用一组例子来,回顾数组指针和指针数组
int arr[5]; //整形数组
int *parr1[10];//指针数组 ,数组10个元素,没个元素是int*
int (*parr2)[10];// 数组指针,指针类型是 int(*)[10],只想的内容是 int [10]指向整形数组
int (*parr3[10])[5];
最后一个 int (*parr3[10])[5],parr3首先跟数组结合,我们可以先将parr3[10]看成一个整体 pa 及int (*pa)[5].这就是一个数组指针,指向int [5],整体再看就是 parr3[10]这个数组有十个元素 每个元素都是一个数组指针 指向int[5];如图所示:
数组参数、指针参数
当我们学会 指针,数组之后,我们传参都会穿数组或者 指针,那应该怎么设计呢??
一维数组传参
#include <stdio.h>
void test(int arr[])//可以用数组接收,不指定元素个数
{}
void test(int arr[10])//指定元素个数
{}
void test(int *arr)//数组名就是地址 也就是指针,我们可以用指针接收
{}
void test2(int *arr[20])//传指针数组,我们就用指针数组接收
{}
void test2(int **arr)//数组名 首元素地址 首元素是 int* 他的地址 我们用二级指针接收一级指针的地址。
{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
}
二维数组传参
void test(int arr[3][5])//二维数组传参,用二维数组接收
{}
void test(int arr[][])//不指定二维数组大小肯定是不行的
{}
void test(int arr[][5])//必须知道步长,所以列得告诉
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//不可以 arr是第一行整个元素地址,也就是数组地址,可以接受但是不好操作
{}
void test(int* arr[5])//指针数组 怎么能接收一个数组地址?肯定错的
{}
void test(int (*arr)[5])//这个可以有 用数组指针 接收整个数组
{}
void test(int **arr)// 用 二级指针接受 整个数组的地址 是不可行的 除非接受某个地址的地址、
{}
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;
}
提醒:
二级指针是存放一级指针地址的;数组的地址 要用数组指针接收。
例如:
int *arr[5]
//传参
test(arr)//数组名 是首元素的地址 首元素是 int* 它的地址 也就是一级指针的地址所以要用二级指针接收
void test(int ** pa)
二级指针传参
#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;
}
函数指针
首先看一段代码:
#include <stdio.h>
void test()
{
printf("hehe\n");
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
我们发现 test函数的地址,和取地址&test的地址是一样的,那函数的地址该如何存储呢,也就是函数指针,首先他得是指针,指向函数:void (*pa)();
让我们来看两端有趣的代码:
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);
分析:代码1
代码2
我们将signal指向的函数拿出 单独看返回类型:
发现它跟函数参数一样,所以我们可以类型重定义一下即
typedef void(*pfun_t)(int)
;需要提醒的是 重定义函数的特点不是将我们重定义的部分卸载后面 typedef void(*)(int) ptfun_t
这种写法是错误的。改完之后我们就可以看得清晰了:
pfun_t signal(int, pfun_t);