提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
提示:这里可以添加本文要记录的大概内容:
指针是编程语言中一个很重要的东西,也是编程的基础之一,学会它,你的代码会更加装x,更加实用
提示:以下是本篇文章正文内容,下面案例可供参考
一、字符指针
字符指针通常用char*来修饰
这里有两种写法
#include<stdio.h>
int main()
{
char* ch = 'w';
char* pstr = "hello bit.";
return 0;
}
这里的第一个指针ch指向字符‘w’。
第二个指针pstr指向的是字符串的第一个字符。
我们来看这样一道题
#include <stdio.h>
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
const char *str3 = "hello bit.";
const char *str4 = "hello bit.";
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则是同时指向一片已经开辟且定义为该字符串的空间地址。
所以str1和str2都是数组首元素的地址,很明显是不相等的,而str3等于str4
二、指针数组
整型数组存放整型
字符数组中存放字符
所以指针数组是存放指针的数组
int* arr1[10];
char* arr2[10];
这里的数组名是arr1和arr2,元素个数为10个,存放的元素类型为int和char
三、数组指针
3.1、数组指针的定义
整型指针指向整型
字符指针指向字符
数组指针指向的是数组,所以本质是指针
int(*p)[10];
首先,*和p先结合,保证p是一个指针,指向一个元素个数为10的数组,数组中的元素数据类型为int型
3.2、关于数组名的一些小tips
#include <stdio.h>
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;
}
从结果可以得出几个结论
1、arr和&arr取出的地址是相同的
2、arr代表的地址是一个四个字节型的,&arr取出的是一个数组大小的
3、arr取出的是数组首元素的地址,&arr取出的是整个数组的地址
==其实在绝大部分的情况下arr出现都是代表数组首元素的地址,有两个特殊情况,sizeof(arr)和&arr。
3.3、数组指针的应用场景
#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
void print_arr2(int(*arr)[5], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
print_arr1(arr, 3, 5);
print_arr2(arr, 3, 5);
return 0;
}
用第二个函数打印时,传进去arr,在二维数组中,arr是首元素地址,而二维数组的首元素地址就是第一行。
所以用一个数组指针来接收
arr[i[[j]就相当于*(*(arr+i)+j)
来看几个有意思的代码
int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];
前三个很简单,那么最后一个怎么解释呢。
首先,parr3首先是和[10]结合,构成一个数组,那么将这一段取出后,剩下的就是这个数组存放的类型,是一个数组指针。
四、关于数组和指针传参的一些问题
4.1、一维数组传参
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);
}
对于test1函数中的传参,不做过多赘述
arr2数组很显然是一个指针数组,里面存放的元素类型是int*,传入数组时可以直接写成相同类型的数组,写成指向第一个int* 元素的二级指针,所以以上写法全部正确
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、一级指针传参
传入一级指针,需要在函数的形式参数中多加注意。
4.4、二级指针传参
同一级指针,不加赘述
五、函数指针
函数也是在内存中开辟空间的,那么也就有指向函数的指针
函数名代表着函数的地址,函数指针我们写成这种形式
void (*fun1)(int,int)
函数的返回类型是void,两个形参类型分别为int int。
给出以下两个代码
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);
我们取出代码1中间代码,发现他是一个函数指针类型的,所以对0强制类型转化为函数指针类型(无参,返回值为void)然后调用在地址0处符合上面条件的函数,指针指向同一个地方。
signal首先和圆括号结合,这是一个函数名,取出这一段后,如图,剩下的是函数的返回类型,所以这是一次对signal函数的声明。
六、函数指针数组
看到这个名字,我们首先要思考,这是一个数组,且里面存放的是函数指针类型的元素,结合数组的构造方式,我们给出以下代码
int (*parr[10])()
首先parr和[10]结合,确定数组身份,int(*)()则是数组内的内容
七、指向函数指针数组的指针
看到这里是不是已经有点晕头转向的感觉了,其实解释一下还是很好理解的
(*parr)[10]//parr是一个指针,指向的元素有10个
void(*)(int,int)//每一个元素是函数指针
结合起来就是
void(*(*parr)[10])(int,int)
八、回调函数
回调函数就是一个通过函数指针来调用的函数,回调函数不是由该函数
的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
九、指针和数组的笔试题
以下全都是32位平台上的结果
(1)
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(*&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));
sizeof里单独放数组名时,测量的是整个数组的大小
当不单独放时,就变成了里面元素的大小
a是首元素地址,解引用后变成首元素
a+1同上
数组内元素
&a取出的是数组的地址,地址的大小为4
&和*想抵消,就是整个数组的大小
&a取出数组的地址,+1后跨越整个数组,但也是指针
指针
指针
(2)
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));
本题同(1),只是字符和整型的大小不同,这里就不过多赘述
(3)
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", strlen(arr));//未知数
printf("%d\n", strlen(arr + 0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr + 1));
printf("%d\n", strlen(&arr[0] + 1));
strlen这个函数是从给定的位置出发,向后直到遇到’\0’停止
所以前两个是未知数
第三和第四个里面给的是字符,传参错误
对于&arr+1,strlen接收的地址往后挪了6个单位,所以是上面的随机值-6
&arr[0]+1则是随机值-1
(4)
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));
只需要注意,传入字符串时,结尾会自动补充一个’\0’,这里给出答案,不在赘述
(5)
char arr[] = "abcdef";
printf("%d\n", strlen(arr));//6
printf("%d\n", strlen(arr + 0));//6
//printf("%d\n", strlen(*arr));//错误
//printf("%d\n", strlen(arr[1]));//错误
printf("%d\n", strlen(&arr));//6
printf("%d\n", strlen(&arr + 1));//随机值
printf("%d\n", strlen(&arr[0] + 1));//5
答案写在代码里了
(6)
char* p = "abcdef";
printf("%d\n", sizeof(p));//4
printf("%d\n", sizeof(p + 1));//4
printf("%d\n", sizeof(*p));//1
printf("%d\n", sizeof(p[0]));//1
printf("%d\n", sizeof(&p));//4
printf("%d\n", sizeof(&p + 1));//4
printf("%d\n", sizeof(&p[0] + 1));//4
这里要注意的是p指向的是字符串首元素的地址
(7)
char* p = "abcdef";
printf("%d\n", strlen(p));//6
printf("%d\n", strlen(p + 1));//5
//printf("%d\n", strlen(*p));//错误
//printf("%d\n", strlen(p[0]));//错误
printf("%d\n", strlen(&p));//随机值
printf("%d\n", strlen(&p + 1));//随机值
printf("%d\n", strlen(&p[0] + 1));//5
(8)
int a[3][4] = {0};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a[0][0]));
printf("%d\n",sizeof(a[0]));
printf("%d\n",sizeof(a[0]+1));
printf("%d\n",sizeof(*(a[0]+1)));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(*(a+1)));
printf("%d\n",sizeof(&a[0]+1));
printf("%d\n",sizeof(*(&a[0]+1)));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a[3]));
二维数组a数组名单独出现在sizeof中,表示的是整个数组
a[0][0]是数组的第一个元素
a[0]是数组第一行的元素名,单独出现在sizeof内部表示数组第一行的一维数组
a[0]没有单独放在sizeof内部,a[0]就表示第一行这个一维数组首元素的地址。
第一行的一维数组首元素地址+1后解引用,找到的就是第一行第二个元素
a没有单独出现在sizeof内部时,表示的是二维数组首元素地址,也就是第一行一维数组的地址。
十、指针笔试题详解
1
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf( "%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
&a取出的是整个数组的地址,进行+1后就来到了数组后面的空间
*(a+1)是数组第二个元素,ptr-1则是越界后的倒推
2
#include<stdio.h>
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
p是一个结构体指针,当p加上一个1,实际上是地址上加了20,得到0x100014
后面就是整型和普通指针的常见加减
3
#include<stdio.h>
int main()
{
int a[4] = { 1, 2, 3, 4 };
int* ptr1 = (int*)(&a + 1);
int* ptr2 = (int*)((int)a + 1);
printf("%x,%x", ptr1[-1], *ptr2);
return 0;
}
对于ptr1,就是一个普通的越界访问之后回来
当a被强制类型转化为int后,它进行+1后就是一个普通的加1(不同于作为地址时加1等于加4个字节)
所以,对于小端存储
最终取出的是0x02000000
4
#include <stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int* p;
p = a[0];
printf("%d", p[0]);
return 0;
}
这里要注意一个坑,定义数组时,大括号中使用的是逗号表达式,a[0]直接使用代表的是第一行首元素的地址
5
#include<stdio.h>
int main()
{
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int* ptr1 = (int*)(&aa + 1);
int* ptr2 = (int*)(*(aa + 1));
printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
return 0;
}
这里方法同上
6
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
a是指针数组,把这个数组中第一个元素的地址给pa,pa++后来到第二个地址
总结
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文只是介绍了指针的一些常见用法,具体实现还需带入实际情况中。