前提复习
- 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
- 指针(指针变量)的大小是固定的4/8个字节(32位平台/64位平台)。
- 指针是有类型,指针的类型决定了指针的±整数的步长,指针解引用操作的时候的权限。
4.指针的运算。(数组元素指针相减等于两者之间的元素个数)
5.内存单元是有编号的,编号 == 地址 ==指针
字符指针
用法一:
#include<stdio.h> int main() { char a = 'w'; char* p = &a; return 0; }
这里的地址使用很简单,下面还有一种用法
#include<stdio.h> int main() { const char* p = "abcdef"; printf("%c", *p); printf("%s", p); return 0; }
这里的p存储的是字符a的地址,我们可以想象成"abcdef"储存在一个数组中,传入p的值为数组名,也就是首元素的地址,就是a
还有一点需要注意的是"abcdef"是一个常量字符串,不能修改,但是会有些人通过*p来修改,这会造成程序崩溃,但是却不会报出警告,往往我们会加一个const 来把错误显示出来
指针数组
指针数组本质上就是一个数组,只是类型不一样而已
比如 字符数组–存放字符的数组
整形数组—存放整形的数组
指针数组—存放指针的数组,存放在数组中的元素都是指针类型
应用1:
#include<stdio.h> int main() { int arr[] = { 1,2,3,4,5,6 }; int arr1[] = { 2,3,4,5,6,7 }; int arr2[] = { 3,4,5,6,7,8 }; int arr3[] = { 4,5,6,7,8,9 }; int arr4[] = { 5,6,7,8,9,10 }; int* arr5[] = { arr,arr1,arr2,arr3,arr4 };//指针数组 int i = 0; for (i = 0; i < 5; i++) { int j = 0; for (j = 0; j < 6; j++) { printf("%d ", arr5[i][j]); } printf("\n"); } return 0; }
这种效果相当于写出了一个二维数组的效果
应用2:
```cpp #include<stdio.h> int main() { char* arr[5] = { "hello bit", "hehe", "penggeC","nitejiuyeke", "C++" }; int i = 0; for (i = 0; i < 5; i++) { printf("%s\n", arr[i]); } return 0; }
这里的效果就相当于写成了一个字符串组成的数组,原理就是数组arr元素保存的是字符串首字符地址,通过遍历取出来,然后一一打印出来
数组指针
指针数组是存放指针的数组,
数组指针是啥呢?,
我们可以想象一下,字符指针就是指向字符的指针, 整形指针就是指向整形的指针,所以,数组指针就是指向数组的指针
#include<stdio.h> int main() { int a = 10; int* p = &a; char b = 'q'; char* pr = &b; int arr[10]; int* pa = arr; //数组名是首元素的地址 //存在两个例外 //sizeof (arr) 这里的arr表示的是整个数组,sizeof(数组名)计算的是整个数组的大小 // &arr 取出的是整个数组的地址 return 0; }
//数组名是首元素的地址
//存在两个例外
//sizeof (arr) 这里的arr表示的是整个数组,sizeof(数组名)计算的是整个数组的大小
// &arr 取出的是整个数组的地址,如果从值的方向考虑就会发现arr和&arr的值是一样的
可以看出数组名和 &数组名的差别,指针类型决定了指针+1 加了几个字节
#include<stdio.h> int main() { int arr[5] = { 1,2,3,4,5 }; int(*p)[5] = &arr;//数组指针,里面的大小必须写 !!!! char arr1[100] = "123456"; char(*pa)[100] = &arr1;//告诉我们pa是一个指针,指向的是char类型的数组地址 char* arr2[5]; char* (*pa2)[5] = &arr2;//告诉我们pa2是一个指针,指向的是char*类型的数组地址 printf("%p\n", p); printf("%p\n", arr); return 0; }
数组指针的写法就是 类型 (p)[数组大小,为了好理解我们可以理解为 char p[10],如果写出这样就会变成指针数组了,为了区分开来,才加()区分,指针类似还是char* 没有改变,只是写法不同而已
#include<stdio.h> int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int(*pa)[10] = &arr; int* p = arr; int i = 0; for (i = 0; i < 10; i++) { printf("%d", (*pa)[i]);//如果使用pa[i] 等价于*(pa + i) } for (i = 0; i < 10; i++) { printf("%d", p[i]); } return 0;
输出的时候我们可以这样理解 arr[i] 可以访问元素, p = arr 所以p[i]可以访问元素
但是pa是数组指针 *pa可以消除& 得到arr , (*pa)[i]可以访问元素
一般我们使用数组指针是在二维数组使用的
在使用之前我们回忆一下一维数组传参给函数时
#include<stdio.h> void print(int* p, int len) { int i = 0; for (i = 0; i < len; i++) { printf("%d ", p[i]); //p[i] == *(p + i) } } int main() { int arr[5] = { 1,2,3,4,50 }; print(arr, 5); return 0; }
p[i] 等价于*(p + i), 所以打印地址一般用 arr + i 或者 &arr[i] 或者 &arr[0] + i
二维数组的应用
#include<stdio.h> void print(int arr[][3], int row, int col) { int i = 0; for (i = 0; i < row; i++) { int j = 0; for (j = 0; j < col; j++) { printf("%d ", arr[i][j]); } printf("\n"); } } void print1(int (*pa)[3], int row, int col) { int i = 0; for (i = 0; i < row; i++) { int j = 0; for (j = 0; j < col; j++) { printf("%d ", pa[i][j]);//pa[i] == *(pa + i) } printf("\n"); } } int main() { int arr[3][3] = { {1,2,3},{2,3,4},{3,4,5} }; print(arr, 3, 3); print1(arr, 3, 3); return 0; }
二维数组是一维数组的数组,我们可以想象一下二维数组arr里面的元素是一维数组,传参arr是首元素的地址,也就是把一维数组的整个地址传进去,
这里使用了数组指针pa ,如果要访问第二个元素可以写成 pa[1]
那我们就再扩展一下
int (*parr2)[10]; int (*parr3[10])[5];
第一行的意思就是,parr2是一个数组指针,指向的数组有10个元素,每个元素为整形
第二行代码的意思就是parr3是一个数组,进行存放数组指针的,可以存放10个数组指针,每个数组指针指向一个元素个数为5的数组,并且这个数组的元素类型为int
总结:这里我们要清楚数组指针的使用,及写法,比如 int (*pa)[10] = &arr
如果arr是一维数组,访问每个元素可以写成(pa)[i],不能写成pa[i],因为pa[i]
等价于(pa + i) ,如果arr为二维数组,我们可以写成pa[i][j]访问每一个元素
数组传参和指针传参
一维数组传参
,数组传参本质上是,传递了首元素的地址数组传参,形参也可以是指针
数组传参,形参接收,形参的形式可以写成arr[] 或者 arr[大小] 或者 指针变量
如果碰见指针传参指针,我们就要思考要使用哪级指针接收,上图就是二级指针接收arr2数组首元素的地址,arr2数组首元素也是存储地址的
二维数传参
一级指针传参
二级指针传参
int* arr[5] int ** p = arr ,这个也可以
总结:指针传参有很多种传入方法,但是传入的意思是一样的
函数指针
数组指针是指向数组的指针,存放的是整个数组的地址 ----&数组名
那么函数指针是指向函数的指针,存放的是函数的地址,
那怎么得到函数的地址呢?
#include<stdio.h> int Add(int x, int y) { return x + y; } int main() { //&函数名就是函数的地址 //函数名也是函数的地址 //这两者没有区别 printf("%p\n", &Add); printf("%p\n", Add); //函数指针 int (*pA)(int, int) = Add;//意思就是pA为函数指针 ,指向的函数的类型为int 参数的类型也为int int (*pA1)(int, int) = &Add; printf("%p\n", pA); printf("%p\n", pA1); int tr = (*pA)(2, 3);// 解引用pA找到函数,并传入参数 int re = pA(2, 3); printf("%d\n", tr); printf("%d\n", re);//这种写法是因为Add和&Add是一样的 ,我们调用函数是Add(2,3),所以pA存储Add,我们也可以使用pA(2,3)效果和调用函数是一样的 return 0; }
可以看出获取函数的地址有两种 &函数名或者函数名
我们调用函数除了使用函数名,还可以使用函数指针调用,使用函数指针调用,我们要熟悉函数调用的写法,Add(2,3),因为函数指针存储的是函数名 Add,所以通过函数指针调用函数,可以写成 pA(2,3),或者(*pA)(2,3)(可以理解为解引用找到函数并传参给函数)
从这个图中我们可以看出函数指针变量的类型
我们从这张图可以看出
代码1的意思就是 把0 强制类型转换成函数指针类型 ,并且解引用找到函数调用,这个函数没有参数
代码2 是一个函数声明,看到这里是不是很吃惊,那我们分析一下,
signal函数有两个参数,第一个参数的类型为int 第二个参数的类型是void()(int),因为不可能是函数调用,当我们吧函数去掉就会发现 void ()(int)是一个函数指针类型,如果我们换个类型就很好理解了 int signal( int, void(*)(int) ); 平时我们函数声明就是这样的,只是这个函数指针类型有写法有点特殊,但不改变这就是一个函数指针类型,
如果觉得这样写很麻烦,我们可以把这个函数指针类型重定义
typedef void(vi_int)(int);注意一下不要写成typedef void()(int) vi_int;这样是不行的
总结:这里我们要熟悉函数指针类型和指针传参 方式有很多但是意思是一样的