3. 数组指针
3.1 数组指针的定义
数组指针是指针?还是数组?
答案是:指针。
我们已经熟悉:
整形指针: int * pint; 能够指向整形数据的指针。
浮点型指针: float * pf; 能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针。
类比:
整型指针 - 指向整型的指针
int a = 10 ; int* p = &a ;
字符指针 - 指向字符的指针
char ch = ’ w ’ ; char* pc = &ch ;
数组指针 - 指向数组的指针
int arr [ 10 ] ; int (*pa ) [ 10 ] = &arr ; //取出的是数组的地址 char arr[ 10 ] ; char (*pc) [ 10 ] &arr ; int * arr [ 5 ] ; int * (*p ) [ 5 ] = &arr ;
下面代码哪个是数组指针?
int *p1[10]; int (*p2)[10]; //p1, p2分别是什么?
解释:
int (p)[10];
//解释:p先和结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。
//这里要注意:[ ]的优先级要高于号的,所以必须加上( )来保证p先和结合。
总结:
指针数组 - 是数组 - 是一种存放指针的数组。
数组指针 - 是指针 - 是一种指向数组的指针 - 存放的是数组的地址。
//指针数组 - 是数组 - 是一种存放指针的数组 //数组指针 - 是指针 - 是一种指向数组的指针 - 存放的是数组的地址 int main() { //指针数组 char* arr[4]; //数组指针 int arr[5]; int (*p)[5] = &arr; return 0; }
3.2 &数组名VS数组名
对于下面的数组:
int arr[10];
arr 和 &arr 分别是啥?
我们知道arr是数组名,数组名表示数组首元素的地址。
那&arr数组名到底是啥?
我们看一段代码:
int main() { int arr[10] = { 0 }; printf("%p\n", arr); printf("%p\n", &arr[0]); printf("%p\n", &arr); return 0; }
运行结果如下:
可见数组名和&数组名打印的地址是一样的。
难道两个是一样的吗?
我们再看一段代码:
//数组名绝大部分情况下是数组首元素的地址 //但是有2个例外: //1. sizeof(数组名) - sizeof内部单独放一个数组名的时候,数组名表示的整个数组,计算得到的是数组的总大小 //2. &arr - 这里的数组名表示整个数组,取出的是整个数组的地址,从地址值的角度来讲和数组首元素的地址是一样的,但是意义不一样 // int main() { int arr[10] = { 0 }; //printf("%d\n", sizeof(arr)); printf("%p\n", arr);//int * printf("%p\n", arr+1);//4 printf("%p\n", &arr[0]);//int* printf("%p\n", &arr[0]+1);//4 printf("%p\n", &arr);//int(*)[10] printf("%p\n", &arr+1);//40 int (*p)[10] = &arr;//p是一个数组指针 //int(*)[10] return 0; }
根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。
实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。(细细体会一下)
本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型
数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40。
注意:
数组名绝大部分情况下是数组首元素的地址
但是有2个例外:
sizeof(数组名) - sizeof内部单独放一个数组名的时候,数组名表示的整个数组,计算得到的是数组的总大小
&arr - 这里的数组名表示整个数组,取出的是整个数组的地址,从地址值的角度
讲和数组首元素的地址是一样的,但是意义不一样
3.3 数组指针的使用
那数组指针是怎么使用的呢?
既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。
看代码:
int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int sz = sizeof(arr) / sizeof(arr[0]); int (* p)[10] = &arr; int i = 0; //p --- &arr //*p --- *&arr //*p --- arr //虽然对,但是不推荐 for (i = 0; i < sz; i++) { printf("%d ", (*p)[i]); } //虽然对,但是不推荐 for (i = 0; i < sz; i++) { printf("%d ", *((*p) + i)); } //使用指针来访问 int* p = arr; for (i = 0; i < sz; i++) { printf("%d ", *(p + i)); } //下标的形式访问数组 for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } return 0; }
一个数组指针的使用:
#include <stdio.h> void print(int arr[3][5], int r, int c) { int i = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 5; j++) { printf("%d ", arr[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; }
用数组指针来做:
#include <stdio.h> void print(int(*arr)[5], int r, int c) { int i = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 5; j++) { //printf("%d ", *(*(arr + i) + j));//arr[i] printf("%d ", arr[i][j]);//arr[i] } 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; }
图片讲解:
学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:
1. int arr[5]; 2. int *parr1[10]; 3. int (*parr2)[10]; 4. int (*parr3[10])[5];
第一个是数组
第二个是指针数组
第三个是数组指针
第四个原理:parr3是数组,数组中存放的指针,该指针指向的又是数组,所以是指针数组里面存放的数组指针
第四个图片讲解:
4. 数组参数、指针参数
在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?
4.1 一维数组传参
#include <stdio.h> void test(int arr[])//ok? {} void test(int arr[10])//ok? {} void test(int* arr)//ok? {} void test2(int* arr[20])//ok? {} void test2(int** arr)//ok? {} int main() { int arr[10] = { 0 }; int* arr2[20] = { 0 }; test(arr); test2(arr2); } }
结果是都可以运行。
结论:
一维数组传参,传参可以是数组,也可以是指针的,当形参是指针的时候,要注意类型。
4.2 二维数组传参
void test(int arr[3][5])//ok?可以 {} void test(int arr[][])//ok?不可以 {} void test(int arr[][5])//ok?可以 {} //总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。 //因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。 //这样才方便运算。 void test(int* arr)//ok?不可以 {} void test(int* arr[5])//ok?不可以 {} void test(int(*arr)[5])//ok?可以 {} void test(int** arr)//ok?不可以 {} int main() { int arr[3][5] = { 0 }; test(arr); }
结论:
二维数组传参
传参可以是指针,也可以是数组
如果是数组,行可以省略,但是列不能省略
如果是指针,传过去的是第一行的地址,形参就应该是数组指针
4.3 一级指针传参
#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; }
思考:
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
4.4 二级指针传参
#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; }
思考:
当函数的参数为二级指针的时候,可以接收什么参数?
5. 函数指针
类比方法:
整型指针 - 指向整型的指针 int*
字符指针 - 指向字符的指针 char*
数组指针 - 指向数组的指针 int arr[ 10 ]* ; int( p )[ 10 ] = & arr ;
函数指针 - 指向函数的指针 int
数组指针中存放的是数组的地址
函数指针中存放的应该是函数的地址
函数有地址吗?
首先看一段代码:
#include <stdio.h> void test() { printf("hehe\n"); } int main() { printf("%p\n", test); printf("%p\n", &test); return 0; }
输出的结果:
输出的是两个地址,这两个地址是 test 函数的地址。
数组:
数组名
&数组名
函数名和&函数名 都是函数的地址,没有区别。
那我们的函数的地址要想保存起来,怎么保存?
下面我们看代码:
void test() { printf("hehe\n"); } //下面pfun1和pfun2哪个有能力存放test函数的地址? void (*pfun1)(); void* pfun2();
首先,能给存储地址,就要求pfun1或者pfun2是指针,那哪个是指针?
答案是:
pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。
举一个例子:
int Add(int x, int y) { return x + y; } //&函数名得到就是函数的地址 int main() { //printf("%p\n", &Add); //printf("%p\n", Add); //pf就是函数指针 int (* pf)(int, int) = Add;//函数的地址要存起来,就得放在【函数指针变量】中 int ret = (*pf)(3, 5); //int ret = Add(3, 5); //int ret = pf(3, 5); printf("%d\n", ret); return 0; }
在练习一个函数指针的例子
char* test(int c, float* pf) { } int main() { char* (*pt)(int, float*) = test; return 0; }
阅读两段有趣的代码:
//代码1 (*(void (*)())0)(); //代码2 void (*signal(int , void(*)(int)))(int);
//代码1 int main() { //1. 将0强制类型转换为void (*)() 类型的函数指针 //2. 这就意味着0地址处放着一个函数,函数没参数,返回类型是void //3. 调用0地址处的这个函数 //下面代码是一次函数调用 (*( void (*)() ) 0)(); return 0; }
将0强制类型转换为void (*)() 类型的函数指针
这就意味着0地址处放着一个函数,函数没参数,返回类型是void
调用0地址处的这个函数
typedef void(*pf_t)(int);//将void(*)(int)类型重新起个别名叫pf_t // typedef void(*pf_t2)(int);//pf_t2是类型名 void(*pf)(int);//pf是函数指针变量的名字 //代码2 int main() { void (* signal(int, void(*)(int) ) )(int); // pf_t signal(int, pf_t); //上述的代码是一个函数的声明 //函数的名字是signal //signal函数的参数第一个是int类型,第二个是void(*)(int)类型的函数指针 //该函数指针指向的函数参数是int,返回类型是void // //signal函数的返回类型也是一个函数指针 //该函数指针指向的函数参数是int,返回类型是void // //void (* signal(int, void(*)(int)))(int) return 0; }
上述的代码是一个函数的声明
函数的名字是signal
signal函数的参数第一个是int类型,第二个是void(*)(int)类型的函数指针
该函数指针指向的函数参数是int,返回类型是void
signal函数的返回类型也是一个函数指针
该函数指针指向的函数参数是int,返回类型是void