目录
指针进阶
指针概念
1,指针就是变量,用来存放地址,地址为一标识一块内存空间
2,指针的大小是固定的4/8个字节(32位平台/64位地址平台)
3,指针式有类型的,指针的类型决定了指针的+—整形的步长,指针解引用时候操作的权限
4,指针的运算
字符指针和字符串指针
字符指针
char ch = 'c';
char *pc = &ch;
字符串指针
char* pc ="hello world";
可以把字符串看作一个含有‘\0’字符的字符数组,那么其实字符串指针是一种特殊的数组指针。
来看一个例子
char str1[] = "hello";
char str2[] = "hello";
char* str3 = "hello";
char* str4 = "hello";
判断str1和str2,str3和str4分别是否相等。
int main()
{
char str1[] = "hello";
char str2[] = "hello";
char* str3 = "hello";
char* str4 = "hello";
if (str1 == str2)
printf("str1==str2");
else
printf("str1!==str2");
if (str3 == str4)
printf("str3==str4");
else
printf("str3!==str4");
return 0;
}
输出:结果来看,str1和str2不相等,str3和str4相等
str1!==str2
str3==str4
原因:str1和str2是分别定义了一个数组,给他们赋值为”hello",所以不想等。
str3和str4是定义指针,指向同一个常量字符串,这个字符串储存在内存中,有自己的地址,指针都指向这个地址,str3和str4自然是相等的.
注意:常量字符串对应的指针不能被修改
char* arr = "hello"; 这种常量字符串不能被修改而且地址固定
指针数组
存放指针的数组
int* arr1[10]; 整形指针数组
char* arr2[4]; 一级字符指针的数组
char** arr3[5]; 二级字符指针的数组
数组指针
数组指针 -- 一个指针,指向数组的指针
比如:
整形指针--是一种指向整形的指针
字符指针--是一种指向字符的指针
定义数组指针的方式
int arr[10] = { 0 };
int (*p)[10] = &arr;
如果不加括号,p会先和【 】结合,会被编译器认为是指针数组。
所以要加括号,保证定义的是指针。
举例:
double* d[5];
double * (*pd) [ 5 ] = &d ; double*--表示指针指向的数组的元素类型是double*;
(*pd)---表示这是一个指针
[5]---表示这是一个数组指针
pd表示一个指针数组指针,这是一个指针,指向一个含有五个元素的数组,数组元素类型为double指针。
数组名是首元素地址;
但是有两个意外:
1,sizeof(数组名) -- 数组名表示整个数组,计算的是整个数组的大小,单位是字节
2,&数组名 -- 数组名表示整个数组,取的是整个数组的地址
数组和指针的一些举例区分
int arr[5]; 整形数组
int* arr[5]; 指针数组
int(*arr)[5]; 数组指针
int(*parr[10])[5]; 数组指针数组
parr是一个存储数组指针的数组
该数组能够存放10个数组指针
每个数组指针指向一个数组,数组5个元素,每个元素是int类型
传递数组的正确方式
一维数组传参
void test(int arr[]) {}
void test(int arr[10]) {}
void test(int *arr) {}
一维(指针)数组传参
void test2(int *arr2[20]) {}
void test2(int **arr2) {}
二维数组传参
void test(int arr[3][5]) {}
void test(int arr[][5]) {}
void test(int (*arr)[5]) {}
二级指针可以传的参数:1,二级指针;2,一级指针的地址;3,一级指针数组名(一级指针数组的首元素地址)
指针
一级指针:
int *p 整形指针
char *ch 字符指针
void *vp 无具体类型指针
二级指针:
char **p;
int **p;
数组指针:指向数组的指针
int(*p)[4];
数组
一维数组;
二维数组
指针数组;
函数指针
函数指针
指向函数的指针:存放函数地址的指针
直接上结论:函数名就是函数地址,就是函数指针
数组名 != &数组名
函数名 == &函数名
举例:
int add(int a, int b)
{
return a + b;
}
int main()
{
printf("%p\n", &add);
printf("%p\n", add);
int (*pf)(int,int) = &add; //pf是一个函数值指针变量
//第一个int表示返回类型; (*pf)表示指针 ;(int ,int)表示参数
//这样一个函数指针就创建完成了
return 0;
}
//输出
005B1023
005B1023
很明显:函数名就是函数地址;
根据上面推理:
pf == &add 而且 &add == add; *pf == add;
可推:pf == *pf;
所以,在函数指针中,*pf与pf是完全相等的,*在函数指针这里可带可不带,带上只是为了便于理解。
即:pf(1,2)与add(1,2)一样。
经过编译器验证,甚至在声明函数指针的时候,也可以不带 * 。
结论:函数指针中的*是个摆设;函数名就是它的指针:存放的是函数地址
示例:
1, 声明一个指向含有10个元素的指针,其中每个元素是一个函数指针,该函数的返回值是int,参数是int*,
int (* (*p)[10] ) (int*)
2,上一段非常经典的代码
分析代码的作用: (* (void ( * ) () ) 0 ) ();
第一步: ( void (*)() )这是一个函数指针类型
( void (*p)() )表示p这个函数指针;去掉p表示函数指针类型;
第二步:把指针类型加括号,并在后面加上0;表明这是一个强制类型转换;把0处的地址转为函数指针类型。
第三步: 加上*表示调用0地址处的函数,()表示无参擦传递;
该函数无参,返回类型为void;
需要注意的是,因为函数指针加不加 * 都可以,所以,第三步的 * 去掉也是正确的。
3,分析这段代码: void (*signal ( int, void ( * )(i nt ) ) ) (int);
void(*)(int)函数指针类型作为变量
signal( int, void(*)(int) )是一个函数指针变量,int 和 void(*)(int)是它的参数
void(* )(int) 也是一个函数指针类型,返回类型是函数指针类型
1,sinal和()先结合,说明signal是函数名
2,signal函数的第一个参数的类型是int,第二个参数的类型是函数指针
该函数指针指向一个参数为int,返回类型为void的函数
3,signal函数的返回类型是一个函数指针
该函数指针指向一个参数为int,返回类型为void的函数
signal是一个函数声明
简化:
通过 typedef - 对类型进行重定义
typedef void(*pfun_t)(int); //对void(*)(int)的函数指针类型重命名为pfun_t;
typedef unsigned int uint; //对unsigned int 重命名为uint
pfun_t signal(int, pfun_t); //与原来的式子是等价的
函数指针的应用
int add(int x, int y)
{
return x+y;
}
int sub(int x, int y)
{
return x - y;
}
int main()
{
int (*pf1)(int, int) = add;
int (*pf2)(int, int) = sub;
int (*pfarr[2])(int, int) = {pf1,sub};//函数指针数组
//函数指针数组可以存放同类型的函数指针
printf("1+2 = %2d\n", pfarr[0](1, 2));
printf("1-2 = %2d\n", pfarr[1](1, 2));
return 0;
}
//输出 1+2 = 3 1-2 = -1
函数指针数组(也叫转移表)可以存放多个同类型函数,这样在调用函数的时候,就会显得更加方便;
函数指针数组指针
先复习一下:
整形数组
int arr[5];
整形数组的指针
int(*p)[5] = &arr;
指针数组(整形)
int* arr[5];
整形指针数组的指针
int* (*p2)[5] = &arr;
p2是指向【整形指针数组】的指针
函数指针数组指针:
int(*p)(int, int); //函数指针
int(*p[5])(int, int); //函数指针数组;
p3 = &p; //取出的是函数指针数组的地址
p3就是一个指向【函数指针数组】的指针
int (* (*p3) [5])(int, int); //函数指针数组的指针
回调函数
回调函数 - 通过函数指针调用的函数
如果你把一个函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
回调函数不是由该函数的实现方式直接调用,而是在特定的事件或者条件发生时由另外的一方调用的,用于对该事件或条件进行响应
这种机制称为回调函数机制
注意跟函数递归地区别:函数递归是自己调用自己,回调是调用其他。
比如:
int add(int x, int y)
{
return x+y;
}
int sub(int x, int y)
{
return x - y;
}
int cal(int (*pf)(int,int))
{
return pf(1,2);
}
int main()
{
int (*pf1)(int, int) = add;
int (*pf2)(int, int) = sub;
printf("1+2 = %2d\n", cal(add));
printf("1-2 = %2d\n", cla(pf2));
return 0;
}
//输出
1+2 = 3
1-2 = -1
指针练习
sizeof(数组名) - 计算的是整个数组的大小
&数组名 - 数组名表示整个数组,取出的是整个数组的地址
除此之外,所有的数组名都是数组首元素地址
int main()
{
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a)); //16 - 计算的是整个数组的大小
printf("%d\n", sizeof(a + 0)); //8 - (a+0)表示第一个元素的地址 - 表示地址的大小(4或者8)(32位或64位)
printf("%d\n", sizeof(*a)); //4 - a表示首元素地址 - 4个字节
printf("%d\n", sizeof(a + 1)); //8 - 表示第二个元素地址
printf("%d\n", sizeof(a[1])); //4 - 表示第二个元素
printf("\n");
printf("%d\n", sizeof(&a)); //8 - &a是数组地址,计算的是地址大小
printf("%d\n", sizeof(*&a)); //16 - 计算的是一个数组的大小
printf("%d\n", sizeof(&a + 1)); //8 - 表示跳过整个数组之后的空间的起始地址
printf("%d\n", sizeof(&a[0])); //8 - 表示第1个元素地址
printf("%d\n", sizeof(&a[0] + 1)); //8 - 表示第二个元素地址
printf("\n");
//字符数组
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr)); //6 - 数组大小
printf("%d\n", sizeof(arr + 0)); //8 - 数组首元素地址
printf("%d\n", sizeof(*arr)); //1 - 解引用 - 数组首元素
printf("%d\n", sizeof(arr[1])); //1 - 数组第二个元素
printf("%d\n", sizeof(&arr)); //8 - 整个数组地址
printf("%d\n", sizeof(&arr + 1)); //8 - 跳过整个数组地址后的空间地址
printf("%d\n", sizeof(&arr[0] + 1)); //8 - 第二个元素地址(首元素地址+1)
printf("\n");
//strlen()函数来计算
//strlen()函数接收的是地址,不能是数字,然后从地址的位置往后查一直算到"\0";
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)); //随机值-6 - 跳过整个数组地址后的空间地址
printf("%d\n", strlen(&arr[0] + 1)); //随机值-1 - 第二个元素地址
printf("\n");
//字符串 - sizeof()
char arr1[] = "abcdef";
printf("%d\n", sizeof(arr1)); //7 - 数组大小
printf("%d\n", sizeof(arr1 + 0)); //8 - 数组首元素地址
printf("%d\n", sizeof(*arr1)); //1 - 解引用 - 数组首元素
printf("%d\n", sizeof(arr1[1])); //1 - 数组第二个元素
printf("%d\n", sizeof(&arr1)); //8 - 整个数组地址
printf("%d\n", sizeof(&arr1 + 1)); //8 - 跳过整个数组地址后的空间地址
printf("%d\n", sizeof(&arr1[0] + 1)); //8 - 第二个元素地址(首元素地址+1)
printf("\n");
printf("%d\n", strlen(arr1)); //6 - 字符串大小
printf("%d\n", strlen(arr1 + 0)); //6 - 字符串大小
//printf("%d\n", strlen(*arr1)); //报错 - 解引用 - 数组首元素
//printf("%d\n", strlen(arr1[1])); //报错 - 数组第二个元素
printf("%d\n", strlen(&arr1)); //6 - 整个数组地址
printf("%d\n", strlen(&arr1 + 1)); //随机值 - 跳过整个数组地址后的空间地址
printf("%d\n", strlen(&arr1[0] + 1)); //5 - 第二个元素地址(首元素地址+1)
printf("\n");
//字符串指针
char* p = "abcdef"; //字符串数组
printf("%d\n", sizeof(p)); //8 -
printf("%d\n", sizeof(p + 1)); //7 -
printf("%d\n", sizeof(*p)); //1 - 解引用 - 表示第一个字符元素
printf("%d\n", sizeof(p[1])); //1 - 表示第二个字符元素
printf("%d\n", sizeof(&p)); //8 -
printf("%d\n", sizeof(&p + 1)); //8 -
printf("%d\n", sizeof(&p[0] + 1)); //8 -
printf("\n");
printf("%d\n", strlen(p)); //6 - 字符串数组的大小(p表示地址)
printf("%d\n", strlen(p + 1)); //5 - 数组首元素地址(p+1表示地址,后移了一个字节)
//printf("%d\n", strlen(*p)); //报错 - 解引用 - 数组首元素
//printf("%d\n", strlen(p[1])); //报错 - 数组第二个元素
printf("%d\n", strlen(&p)); //随机值 - 指针的地址
printf("%d\n", strlen(&p + 1)); //随机值 - 指针地址的下一个地址
printf("%d\n", strlen(&p[0] + 1)); //5 - 第二个字符元素地址(首元素地址+1)
//char ch = 'q';
char* pc = "hello world";
//本质上是把字符串的首字符地址存储到了pc中
printf("%c\n", *pc);
printf("%p\n", pc);
printf("%c\n", pc[0]);
printf("%c\n", pc[10]);
printf("%s\n", *pc); //这一句有问题;
return 0;
}
指针题
指针联系2
二维数组
int main()
{
int a[3][4] = { 0 };
printf("%d\n", sizeof(a)); //48 - 表示整个数组
printf("%d\n", sizeof(a[0][0])); //4 - 第一行第一列的元素
printf("%d\n", sizeof(a[0])); //16 - 当访问某一行的时候,相当于一个一维数组,是该行数组的大小
printf("%d\n", sizeof(a[0] + 1)); //8 - a[0]并没有单独放在内部,也没有取地址,所以a[0]表示第一行第一个元素的地址
printf("%d\n", sizeof(*a[0] + 1)); //4 - 第一行第二个元素
printf("%d\n", sizeof(a + 1)); // 8 - 第二行的地址
printf("%d\n", sizeof(*(a + 1))); //16 - (a+1)表示第二行的地址,*(a+1)表示第二行的数据;表示数组的第二行 // *(a+1) == a[1]
printf("%d\n", sizeof(&a[0] + 1)); //8 - 表示地址(a[0]是第一行的数组名;&a[0]表示第一行的地址;+1表示第二行的地址)
printf("%d\n", sizeof(*(&a[0] + 1))); //16 - 表示第二个一维数组;(表示第二行数据)
printf("%d\n", sizeof(*a)); //16 - 表示元素 (第一个一维数组)(第一行的数据)
printf("%d\n", sizeof(a[3])); //16 - 不需要访问这个a[3];
//总结一个规律:当访问二维数组的某一行的时候,数组把行看作一个元素
//这时候把二维数组看作一维数组,那么它的元素就是一个个以为数组a[0] -- a[2];
//sizeof(a[0]),就表示该一维数组的大小
return 0;
}
数组本质上是一个指针,数组指针和数组名打印的地址是一样的
区别:
数组名一般来讲表示,数组首元素地址;
有两个例外:
1,sizeof(arr)这里计算的是整个数组的大小; 2,&arr表示的是整个数组的地址
数组指针指的就是整个数组
总结:
1,sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小
2,&数组名,这里的数组名表示整个数组,取出的是整个数组的地址
3,除此之外所有的数组名都表示首元素地址
指针笔试题:
//第一题,
int main()
{
int a[5] = { 1,2,3,4,5 };
int* p = (int*)(&a + 1); //p表示跳过数组a之后的地址,并进行指针类型转换为int*类型
printf("%d,%d\n", *(a + 1), *(p - 1));//p-1 表示地址往前数4个字节;再解引用,就是a[4];
printf("%d,%d\n", *(a + 1), *(p));
return 0;
}
//输出:2,5
//第二题,考察的是:指针类型决定了你的运算
struct test {
int num;
char* pcname;
short cha[2];
}*p;
int main()
{
printf("%d\n", sizeof(*p));
printf("%p\n", p);
printf("%p\n", p + 0x1); //指针+1跳过了24个字节(结构体类型)的大小
printf("%p\n", (unsigned long)p + 0x1); //转换为一个长整形数字,不再是指针了,数字+1就是+1,与指针不一样
printf("%p\n", (unsigned long*)p + 0x1);
printf("%p\n", (long long*)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1); //指针+1跳过了一个int类型的字节数(4)个
return 0;
}
//输出:24
//0000000000000000
//0000000000000018
//0000000000000001
//0000000000000004
//0000000000000004
//第三题,
int main()
{
int a[4] = { 1,2,3,4 };
int* p = (int*)(&a + 1);
int * p1 = (int*)((int)a + 1);
printf("%x\n", p[-1]);
printf("%x\n", *p1);
printf("%x,%x\n", p[-1], *p1);
return 0;
}
//输出:4,2000000(0x0200_0000)
//第四题
int main()
{
int a[3][2] = { (0,1),(2,3),(4,5) };
//()表示逗号表达式
//数组实际上为{1,3,5)
int* p;
p = a[0]; //数组首元素地址
printf("%d", p[0]);
return 0;
}
//输出:1
//第五题,
int main()
{
int a[5][5] = { 0 };
int i = 0;
int j = 0;
for (i = 0; i < 5; i++)
{
for (j = 0; j < 5; j++)
{
a[i][j] = 5 * i + j;
}
}
int(*p)[4]; 本质上,二维数组就是以恶以及数组指针。
p = &a;
//p = &a;
printf("%p\n", *(p + 1));
printf("%p\n", p + 1);
printf("%p\n", p[1]);
printf("%d\n", *(*(p + 1)));
printf("%d\n", *(p[1]));
printf("%d\n", a[0][0]);
printf("%d\n", a[4][4]); //4*5+4 = 24
printf("%d\n", p[1][1]); //1*4+1 = 5
printf("%d\n", p[4][2]); //4*4+2 = 18
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
printf("%p,%d\n", &a[4][2] - &p[4][2], &a[4][2] - &p[4][2]);
return 0;
}
//输出:fffffffffffffffc,-4
// 0000000000000004, 4
//因为数组指针+1表示跳过一整个数组
//第六题,
int main()
{
int a[2][5] = { 1,2,3,4,5,6,7,8,9,10 };
int a0[2][2][2] = {1,2,3,4,5,6,7,8};
int a1[] = {1,2,3,4,5,6,7,8,9,10};
int* p1 = (int*)(&a + 1);
int* p2 = (int*)(*(a + 1));
printf("%d\n", ***a0);
printf("%d\n", **a);
printf("%d\n", *a1); //可以这样理解:数组就是指针;一维数组就是一级指针;二维数组就是二级指针,需要解引用两次才能获得数据
printf("%p\n", a[1]);
printf("%p\n", p2);
printf("%d\n", *a[1]);
printf("%d,%d", *(p1 - 1), *(p2 - 1));
return 0;
}
//10,5
//第七题。
int main()
{
char* a[] = { "at", "alibaba","work","zbcd"}; //指针数组:存储了这四个字符串的地址
char** pa = a; //二级指针指向指针数组的首元素地址;首元素是一个指向work的指针a[0]
pa++; //二级指针+1跳过一个一级指针
pa++;
printf("%p\n", a);
printf("%p\n", a[0]);
printf("%p\n", a[1]);
printf("%p\n", a[2]);
printf("%d\n", (char*)(a[1])- (char*)(a[0]));
printf("%s\n", *pa);
printf("%c\n", **pa);
return 0;
}
//输出:at
//因为字符串数组很特殊;可以直接写:char*p = "abc";
//第八题,
int main()
{
char* c[] = { "enter","new","point","first" };
//指针数组,内含4个指针,指向4个字符串
char** cp[] = { c + 3,c + 2,c + 1,c };
//c表示数组首元素地址,也就是第一个指针的地址,实际上是一个二级指针
//cp是一个二级指针数组,内部表示四个二级指针,内部是逆序指向c的4个指针的
char*** cpp = cp;
//cpp表示指针数组,可以看作一个三级指针,指向二级指针数组cp[0]
printf("%s\n", *c+1);
printf("%s\n", **++cpp);
//先++再引用;++cpp表示跳过这个二级指针数组即为指向cp【1】== c[2];解引用两次得到字符串数组;
//输出:point
printf("%s\n", *-- * ++cpp + 3);
//*--(*++cpp) +3
//++cpp ==> cp +2 ==>解引用后:就是c+1; ==> 这是对cp的元素进行操作--之后就是c表示cp【2】 ==c(数组c首元素地址:表示第一个指针的地址);==> 再解引用就是获得字符串数组“enter”的指针;
// ;因为这个指针是char*类型的;当+3的时候会跳过3个char指针;==>所以这个指针会从er开始打印;结果为er
//输出:er
printf("%s\n", *cpp[-2] + 3);
//cp+2-2 ==》cp ,把cp解引用表示c+3;再解引用表示指向“first”的指针;再+3表示;跳过3个char*类型的指针,所以,结果为st
//输出:st
printf("%s\n", cpp[-1][-1] + 1);
//*(*(cpp-1)-1) +1
//cp+2-1 == cp+1==> 解引用之后为c+2 ;在-1之后表示c+1==> 解引用表示指向“new”的指针;==> +1表示“ew”
//输出:ew
return 0;
}
//小知识:
//数组指针相当于一个二级指针
//字符串虽然是一个数组,但是字符串指针也是一级指针,不能算二级指针
//字符串指针数组是二级指针
//普通数组指针需要解引用两次;字符串指针只需要解引用一次
int main()
{
char* p = "hello word";
int a[4] = { 1,2,3,4 };
int(*arr)[4] = a;
printf("%c\n", *p);
printf("%s\n", p);
printf("%d\n", **arr);
return 0;
}