再开始之前,我们先来回顾一下什么是指针:
1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
指针就是地址(一般口头称指针变量为指针) ,地址是具备唯一属性的,不可能存在相同的地址,指向的位置是不一样的空间
无论该地址是什么类型的,这个地址所指向的空间存放了多少多少东西,地址的大小就是4/8个字节,只和平台的操作系统位数有关系
指针的类型就决定了,该指针在做加法或者减法的时候,跳过的字节数
1. 字符指针
我们知道字符指针的数据类型是:
char *--字符指针类型
一般使用:
int main
{
char num = 'a';
char* p = #
printf("%c", num);
printf("%c", *p);
return 0;
}
还有一种使用方式是:
int main()
{
char* p = "abcdefg";
printf("%s\n", p);
return 0;
}
代码结果:
abcdefg
这里我们看到,我们直接把"abcdef"这个字符串直接赋给了字符指针变量p
我们知道指针变量只可以存放地址,所以p变量里面放的一定不是''abcdef",那放的会不会是该字符串的地址呢,那到底是该字符串的第一个元素的地址还是整个字符串的地址呢?因为我们知道在C语言中是有首元素地址和全地址的概念的
int main()
{
char* p = "abcdefg";
printf("%c\n", *p);
printf("%c\n",*(p+6));
return 0;
}
运算结果为:
a
g
到这里我们知道p字符指针变量里面存放的的是该字符串首元素的地址
接下来我们再看一道笔试题:
首先我们知道该代码里面该代码里面有俩个字符数组和俩个字符指针
其次,str1和str2不能应为他们俩个存放的元素相同就认为,他们的首元素地址就一样。元素是存放在数组里面的,而数组的空间开辟是不可能一样的,就拿str1和str2来说,他们的地址是不可能一样的,那么俩个数组的地址都不一样,存放在里面的元素地址又怎么可能一样呢
但指针变量就不一样了,指针变量指向的是地址,俩个不同的指针变量是可以指向同一个地址的
所以这个代码的结果就是:
str1 and str2 are not same
str3 and str4 are same
2. 指针数组
我们知道整形数组、字符数组等,相应的他们都是存放整形、字符的数组
那么指针数组存放的也就是指针了
指针数组的类型:int *p[ ]
实例显示:
int main()
{
int* p[4];
char* p1[4];
float* p2[4];
return 0;
}
2.1 指针数组的使用
当我们知道了指针数组这个概念后,我们就可以将相同类型的数组的地址存放在指针数组里面,统一调配使用
int main()
{
int arr1[] = { 1, 2, 3};
int arr2[] = { 4, 5, 6};
int arr3[] = { 7, 8, 9};
int* p[3] = { arr1,arr2,arr3 };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
printf("%d ", p[i][j]);
}
printf("\n");
}
return 0;
}
代码结果:
1 2 3
4 5 6
7 8 9
这里注意一下,一定要相同数据类型的数组才可以
我们将arr1、arr2、arr3这三个数组名放入p指针数组中,就相当于把三个数组的函数首地址放到了p指针数组中,那么我们p[1][2]的意思就是,先p[1]定位到p指针数组中的第1个元素位置,定位到第1个元素后就拿到了arr1这个数组名,也就是arr1的首元素地址
注意此时 p[1] == arr1
所以,p[1][2] == arr1[2],所以我们最后拿到的就是arr1中的第二个元素2
3. 数组指针
不知道数组指针是什么没关系,我们先聊聊整型指针、字符指针
什么是整形指针?就是存放整型变量地址的指针,叫做整型指针,指针每++、--的跨度都是4个字节,一个整形指针只能存放一个整形变量的地址
什么是字符指针?就是存放字符变量地址的指针,叫做字符指针,指针每++、--的跨度都是1个字节,一个字符指针只能存放一个字符变量的地址
那么什么是数组指针呢?没错,就是存放数组地址的指针,叫做数组指针,改指针每++、--的跨度就是地址指向的那个数组的大小,当然数组指针也只能存放一个数组的地址,也就是说数组指针的指向对象是唯一的
接下来看一下数组指针的类型:
数组指针的类型:int (*p)[ ]
一定要注意的是,数组指针存放的是数组的地址,不是数组的首元素地址
存放数组地址--数组指针类型:void (*p) [ ]
存放指针的数组--指针数组类型:void* p[ ]
再讲数组指针的具体使用之前,得先了解一下数组名和&数组名
3.1 数组名和&数组名
int main()
{
int arr[10] = { 0 };
printf("%p\n",arr);
printf("%p\n",&arr);
return 0;
}
看代码结果:
000000F99B99F5A8
000000F99B99F5A8
看到这个结果,你是不是觉得arr和&arr是等价的都是数组首元素地址?
我们前面讲过了,指针类型决定了指针变量在++、--时的跨度距离,如果它们都表示首元素地址的话,那么应该都是int* 类型的,跨度就是4个字节,OK分析到这里,我们接着看代码:
int main()
{
int arr[10] = { 0 };
printf("%p\n",arr+1);
printf("%p\n",&arr+1);
return 0;
}
代码结果:
0000001404DEF66C
0000001404DEF690
好像并不是我们刚刚预想的那样,那也就是说&arr和arr的类型不一样
我们看看这两个地址是不是只相差了36个字节,因为我们是int类型的数组,一个元素4 个字节,那是不是&arr+1比arr+1多跳过了9个字节,也就是说&arr+1跳过了一个数组的大小,那&arr不就是我们刚刚说的数组指针类型嘛
&arr代表的就是该数组的数组地址,arr代表的就是该数组的首元素地址
4. 数组参数、指针参数
4.1. 一维数组传参
一维数组传参本质就是将一维数组的地址传递出去,一般都是首元素的地址或者是整个数组地址
这里我们拿题目来一一讲解:
在做题目前,在回顾一下,在一维数组的前提下,数组名传参那么传过去的就是该数组的首元素的地址,如果是&数组名那么就是该数组的全地址
这里将看到test(arr),很明显这里传过去的参数是数组的首元素地址,那么test函数用int arr[ ]来接收是可以的,因为此时void test(int arr[ ])中的arr相当一个指针,所以当你去将arr的首元素地址传入test函数中的时候,相当于将首元素地址传入一个指针来接收,所以是可以的
(那这个arr,此时的[ ]的作用是将arr在某种程度上变成了指针,虽然变量+[ ]可以变成数组,但他也改变了这个变量,使他变成了数组的入口地址,存放地址的不就是指针吗,再加上这个test函数不会真的开辟一个数组空间来存放这个参数,所以[ ]用在接收参数的时候,他的作用就是将他前面这个变量名,变成指针来存放要接收的地址)
test函数会将接受到的参数,进行复制
那我们传的是首元素地址,所以他复制的也是首元素的地址,这里提一下,虽然我们在test函数中接受参数的是int arr[ ] 或者int arr[10],但是函数不会真的去开辟一个数组去存放它,虽然这里用的是arr [10]但是这个时候,操作符[ ]的作用是使arr变成数组名,arr也就变成了指针,那么数组名就可以接受传来的首元素地址
既然不会真的开辟一个数组空间来存放参数,那么这个[10]中的10还重要吗?我传给你的就是一个首元素地址,计算机也不会开辟一个数组空间来存放这个首元素地址,那么这个10对于计算机来说,其实是没有意义的,所以哪怕你在这里填100、1000、10000...也好,计算机是不会管的,直接无视的,所以这样是不是就和第一道题一样了
刚刚说了传的参数是一个地址,那么test函数用一个指针来接收,所以也是可以的
虽然这里传入的参数是一个指针数组的首元素地址(数组名),是用来访问该指针数组第一个元素的,那我用一个指针数组来接收,内外保持一致,当然也可以接收啦
虽然这里传入的参数是一个指针数组的首元素地址(数组名),但是他终究是一个地址啊,那我用一个二级指针来接收,也可以啊我解引用这个二级指针是不是就可以得到那个首元素地址了(只要保证类型一致)
4.2 二维数组传参
这里提一下二维数组的数组名表示第一行的整个一维数组地址
开始讲解:
二维数组传参要么是用指针来接受,那么就是用数组来接收,并且不能省略列的长度
所以,很显然第二个就不可以。
为什么不能省略列,而可以省略行,是因为只要知道你每行有几个元素,那么行数自然就知道了
二维数组的首元素地址是第一行的一维数组的整个数组地址,那么如果要存放这个数组地址那么只能用数组指针( int (*p)[ ] )
第一个题目,数据类型都对不上,怎么可能存的进去,他是int *类型,而我们数组地址是int (*p)[ ]
第二个题目,它是一个指针数组类型,而传的参数是数组指针类型,显然不可以
第三个题目,首先他是一个数组指针类型,其次后面他也带了列的大小,所以这个是可以的
还是类型不对,数组指针类型怎么可能可以用耳机整形指针来接受
5. 函数名、函数指针
5.1函数名是函数地址
函数肯定有地址肯定没错,数组的数组名就是数组的首元素地址,也就是数组的访问入口,那函数名会不会也是函数的访问入口呢?
看代码:
void test()
{
printf(" ");
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
运行结果:
00007FF610A61159
00007FF610A61159
根据代码我们可以知道原来函数名就是函数地址
5.2 函数指针
函数指针--很显然是存放函数地址的指针,函数指针的类型是:
6.1 函数指针数组
函数指针数组--是用来存放多个相同类型的函数地址(函数指针)的数组
先看它的类型:
如果是int类型,那么后面()内应该是int
看代码:
#include<stdio.h>
int test(int x)
{
;
}
int test1(int x)
{
;
}
int test2(int x)
{
;
}
int main()
{ //打印函数地址
printf("test = %p\ntest1 = %p\ntest2 = %p\n", test, test1, test2);
int (*p[])(int) = { test,test1,test2 };
printf("数组内的元素:\n");
for (int i = 0; i < 3; i++)
{
printf("%p\n", *p[i]);
}
return 0;
}
代码结果:
test = 00007FF7A2651159
test1 = 00007FF7A26513A7
test2 = 00007FF7A2651208
数组内的元素:
00007FF7A2651159
00007FF7A26513A7
00007FF7A2651208
所以这里可以看出,函数指针数组,就是存放相同类型的函数地址(函数指针) 的数组
7.1 指向函数指针数组的指针
指向函数指针数组的指针,指针?那就是地址喽,地址的类型无非就是int*,char*,short*, float*等,大小就是无非4/8个字节
指向函数指针数组的指针,不就是将函数指针数组的地址存到一个相同类型的指针里面嘛
看代码:
#include<stdio.h>
int test(int x)
{
;
}
int test1(int x)
{
;
}
int test2(int x)
{
;
}
int main()
{
int (*p[])(int) = { test,test1,test2 };
int* p1 = p;//将函数指针数组的入口地址赋值给p1
printf("p1 = %p\np = %p", p1,p);
return 0;
}
代码结果:
p1 = 000000955F14F978
p = 000000955F14F978
此时这个p1就是指向函数指针数组的指针了