目录
指针基础:
- 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
- 指针的大小是固定的4/8个字节(32/64位平台)。
- 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作时候的权限。
- 指针+-整数、指针-指针、指针的关系运算。
字符指针
p存放的是字符串首字母地址
常量字符串不可修改,两个字符串又相等,所以p1和p2同时指向“abcdef”
“abcdef”分别初始化了两个数组,arr1和arr2是两块不同的空间,所以arr1和arr2首元素地址不等
指针数组
整形数组 int arr[10]; 存放整形的数组
字符数组 char arr2[5]; 存放字符的数组
指针数组 存放指针的数组
int* arr[10]; 存放整形指针的数组
char* ch[5]; 存放字符指针的数组
指针数组模拟二维数组:
parr[i][j] 还可以写成 *(parr[i]+j)
数组指针
数组指针是 指针
整形指针 - 指向整形的指针,存放整形变量地址的
数组指针 - 指向数组的指针
int(*p)[10]; p和*先结合,说明p是一个指针变量,然后指向的是一个大小为10个整形的数组,所以p是一个指针,指向一个数组,叫数组指针。(注:[]的优先级大于*)
数组名该怎么理解呢?通常情况下,我们说的数组名都表示数组首元素的地址,但是有两个例外:
- sizeof(数组名),这里的数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小
- &数组名,这里的数组名表示整个数组,取出的是整个数组的地址
数组指针如何使用?
但这样用起来很别扭,所以数组指针不用在一维数组上
用在二维数组上:
void print2(int(*p)[5], int c,int r)
{
for (int i = 0; i < c; i++)
{
for (int j = 0; j < r; j++)
{
printf("%d ", *(*(p + i) + j));
}
printf("\n");
}
printf("\n");
}
int main()
{
int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
//写一个函数打印arr数组的内容
print2(arr,3,5);
}
*(*(p + i) + j)) 中,p+i是指向第i行的,*(p+i)相当于拿到了第i行,也相当于第i行的数组名,数组名表示首元素的地址,*(p+i)就是第i行第一个元素的地址,也可以写作p[i][j],本质上编译器还是得转换成*(*(p + i) + j))
int(* parr3[10])[5];的意思:parr3是一个数组,数组有10个元素,每个元素的类型是:int(*)[5],parr3是存放指针数组的数组
数组参数、指针参数
一维数组传参
int main()
{
int arr[10] = { 0 };
test(arr);
}
形参写成数组的形式:(本质上传的是首元素地址,和行列具体是几无关)
- void test(int arr[10]){}
- void test(int arr[]){} 形参部分的数组大小可以省略
- void test(int arr[100]){} 不建议,但是没错
形参写成指针的形式:
- void test(int* p){}
int main()
{
int* arr2[20] = { 0 };
test2(arr2);
}
形参写成数组的形式:
- void test2(int* arr[20]){}
- void test2(int* arr[]){}
形参写成指针的形式:(首元素是int*)
- void test2(int* *p){}
二维数组传参
int main()
{
int arr[3][5] = { 0 };
test(arr);
}
形参写成数组的形式:
- void test(int arr[3][5]){}
- void test(int arr[][5]){} 行可以省略,列不能省略
形参写成指针的形式:(二维数组首元素的地址是第一行的地址)
- void test(int(*p)[5]){}
函数指针
函数指针 - 指向函数的指针
两个虽然写法不同,但意义完全相同
int(*pf)(int,int) = Add;
pf就是函数指针变量
例:
函数指针的用法:
(*pf)里的*其实是没有太大意义的
分析代码1:
( *( void (*)() )0 )()
void(*)() 是函数指针类型
( void(*)() )0 对0进行强制类型转换
首先是把0强制类型转换为一个函数指针类型,这就意味着0地址处放一个返回类型是void,无参的一个函数,然后调用0地址处的这个函数
分析代码2:
void (*signal (int, void(*) (int) ) ) (int)
signal是一个函数的声明,signal函数的参数第一个是int类型的,第二个是 void(*)(int) 的函数指针类型,signal函数的返回值类型也是void(*)(int)
可以这样理解:void(*)(int) signal(int, void(*) (int)),但这样表示是错误的
简化:typedef void(* pf_t)(int); 给函数指针类型void(*)(int)重新起名叫pf_t,pf_t signal(int, pf_t);
函数指针数组
函数指针数组的用途:转移表
使用转移表可以替代冗长的switch-case语句
例:计算器 不用转移表的代码:
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("ret = %d\n", ret);
break;
case 2:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("ret = %d\n", ret);
break;
case 3:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = mul(x, y);
printf("ret = %d\n", ret);
break;
case 4:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = div(x, y);
printf("ret = %d\n", ret);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
使用转移表简化后:
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
while (input)
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
if ((input <= 4 && input >= 1))
{
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = (*p[input])(x, y);
}
else
printf("输入有误\n");
printf("ret = %d\n", ret);
}
return 0;
}
指向函数指针数组的指针
解引用找到数组元素:int ret = (*p3)[i](x, y);
回调函数
回调函数就是一个通过函数指针调用的函数。
写了一个A函数,没有直接调用它,而是把A函数的地址传给了B函数,在B函数里用一个函数指针接收A函数的地址,在B函数里通过函数指针调用A函数时,A函数被称为回调函数。
使用回调函数简化计算器代码:
qsort是一个库函数,基于快排算法实现的一个排序的函数
其中的比较函数要求qsort函数的使用者,自定义一个比较函数
- 排序的整形数据:用>、<
- 排序的结构体数据:可能不方便直接使用>、<比较了,使用者根据实际情况,提供一个函数,实现2个数据的比较
例:使用qsort排序数组
使用qsort排序结构体:
按照姓名排序(是按首字母顺序排):
使用冒泡的方式模拟qsort函数:
指针和数组题目解析
sizeof计算的是对象所占内存大小,单位是字节,返回值类型是size_t;不在乎内存中存放的是什么,只在乎大小;sizeof是一个操作符;sizeof在64位平台上应用%llu打印。
strlen是一个库函数;只能求字符串长度,从给定的地址向后访问字符,统计\0之前出现的字符个数。
例1:一维数组int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
结果是:4byte*4 = 16
printf("%d\n",sizeof(a+0));
4/8,a+0是数组第一个元素的地址,是地址,大小就是4/8个字节
printf("%d\n",sizeof(*a));
4,a表示数组首元素的地址,*a表示数组的第一个元素,sizeof(*a)就是第一个元素的大小4
printf("%d\n",sizeof(a+1));
4/8,a表示数组首元素的地址,a+1表示数组第二个元素的地址,sizeof(a+1)就是第二个元素的地址的大小
printf("%d\n",sizeof(a[1]));
4,计算的是第二个元素的大小
printf("%d\n",sizeof(&a));
4/8,&a取出的是数组的地址,数组的地址也是地址,是地址,大小就是4/8字节
printf("%d\n",sizeof(*&a));
16,计算的是整个数组大小,相当于sizeof(a)
printf("%d\n",sizeof(&a+1));
4/8,&a是数组的地址,+1跳过整个数组,产生的是4后边位置的地址
printf("%d\n",sizeof(&a[0]));
4/8,取出的是数组第一个元素的地址
printf("%d\n",sizeof(&a[0]+1));
4/8,数组第二个元素的地址
例2:字符数组char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));
结果是:6
printf("%d\n", sizeof(arr+0));
4/8,arr+0是数组首元素的地址
printf("%d\n", sizeof(*arr));
1,*arr是首元素,首元素是一个字符
printf("%d\n", sizeof(arr[1]));
1,arr[1]是数组的第二个元素,大小是1个字节
printf("%d\n", sizeof(&arr));
4/8,&arr是数组的地址,是地址
printf("%d\n", sizeof(&arr+1));
4/8,是从数组的地址开始向后跳过了整个数组产生的一个地址
printf("%d\n", sizeof(&arr[0]+1));
4/8,&arr[0]+1是数组第二个元素的地址
printf("%d\n", strlen(arr));
>=6的随机值,arr数组中没有\0,所以strlen函数会继续往后找\0,统计\0之前出现的字符个数
printf("%d\n", strlen(arr+0));
随机值,arr+0还是数组首元素地址
printf("%d\n", strlen(*arr));
error,arr是数组首元素的地址,*arr是数组的首元素,‘a’->97,strlen会把97当做地址,从97开始向后数字符
printf("%d\n", strlen(arr[1]));
error,相当于传进‘b’
printf("%d\n", strlen(&arr));
随机值,也是从首元素开始数
printf("%d\n", strlen(&arr+1));
随机值,跳过一个数组开始数
printf("%d\n", strlen(&arr[0]+1));
随机值,从‘b’开始数
例3:字符数组char arr[] = "abcdef";
printf("%d\n", sizeof(arr));
7,f后有\0
printf("%d\n", sizeof(arr+0));
4/8,arr+0是数组首元素地址
printf("%d\n", sizeof(*arr));
1,*arr是数组的首元素
printf("%d\n", sizeof(arr[1]));
1,arr[1]是数组的第二个元素
printf("%d\n", sizeof(&arr));
4/8,&arr是数组的地址,是地址
printf("%d\n", sizeof(&arr+1));
4/8,&arr+1是\0后边的地址
printf("%d\n", sizeof(&arr[0]+1));
4/8,&arr[0]+1是数组第二个元素的地址
printf("%d\n", strlen(arr));
6,数的是\0之前的个数
printf("%d\n", strlen(arr+0));
6,也是首元素地址
printf("%d\n", strlen(*arr));
error,‘a’非法访问
printf("%d\n", strlen(arr[1]));
error
printf("%d\n", strlen(&arr));
6,也是从首元素开始数
printf("%d\n", strlen(&arr+1));
随机值,跳过了\0向后数
printf("%d\n", strlen(&arr[0]+1));
5,从‘b’开始数
例4:char *p = "abcdef"; p存放的是首元素地址
printf("%d\n", sizeof(p));
4/8,p是指针变量,计算的是指针变量的大小
printf("%d\n", sizeof(p+1));
4/8,p+1是‘b’的地址
printf("%d\n", sizeof(*p));
1,*p就是‘a’
printf("%d\n", sizeof(p[0]));
1,p[0] -> *(p+0) -> *p
printf("%d\n", sizeof(&p));
4/8,&p是指针变量p在内存中的地址
printf("%d\n", sizeof(&p+1));
4/8,&p+1是跳过p,p最后的地址
printf("%d\n", sizeof(&p[0]+1));
4/8,&p[0]是a的地址,&p[0]+1就是b的地址
printf("%d\n", strlen(p));
6,从a开始数字符
printf("%d\n", strlen(p+1));
5,从b的位置开始向后数
printf("%d\n", strlen(*p));
error,‘a’非法访问
printf("%d\n", strlen(p[0]));
error,p[0]=*p
printf("%d\n", strlen(&p));
随机值,和字符串没关系,数的是p的地址
printf("%d\n", strlen(&p+1));
随机值
printf("%d\n", strlen(&p[0]+1));
5,从b的位置开始向后数字符,p[0]=*p,&p[0] = ‘a’
例5:二维数组int a[3][4] = {0};
可以把二维数组想象成一维数组,二维数组的每一行是一个一维数组的一个元素
printf("%d\n",sizeof(a));
48,计算的是整个数组的大小,单位是字节,3*4*4byte
printf("%d\n",sizeof(a[0][0]));
4,第一行第一个元素的大小
printf("%d\n",sizeof(a[0]));
16,a[0]是第一行的数组名,sizeof(a[0])就是第一行的数组名单独放在sizeof内部,计算的是第一行的大小
printf("%d\n",sizeof(a[0]+1));
4/8,a[0]没有单独放在sizeof内部,也没有被取地址,表示的是第一行第一个元素的地址,+1表示的是第二个元素的地址
printf("%d\n",sizeof(*(a[0]+1)));
4,表示的是第一行第二个元素
printf("%d\n",sizeof(a+1));
4/8,a表示首元素的地址,就是第一行的地址,所以a+1就是第二行的地址
printf("%d\n",sizeof(*(a+1)));
16,对第二行的地址解引用访问到的就是第二行,也可以理解为*(a+1) -> a[1]
printf("%d\n",sizeof(&a[0]+1));
4/8,a[0]是第一行的数组名,&a[0]取出的就是第一行的地址,&a[0]+1就是第二行的地址
printf("%d\n",sizeof(*(&a[0]+1)));
16,对第二行的地址解引用访问到的就是第二行
printf("%d\n",sizeof(*a));
16,a就是首元素地址,就是第一行的地址,*a就是第一行
printf("%d\n",sizeof(a[3]));
16,类型是int[4],并没有访问该地址
总结:arr[i]只是形式上这么写(为了方便初学者理解),本质上是*(arr+i)
指针题
例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;
}
结果为:2,5
例2:假设p的值为0x100000。 如下表表达式的值分别为多少(x86环境)?(已知,结构体Test类型的变量大小是20个字节)
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
int main()
{
p=(struct Test*)0x100000;
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
结果为:00100014;00100001;00100004
p是结构体指针,+1加的是1*sizeof(结构体)大小;p被强制类型转换成unsigned long,变成一个整形,整形+1加的就是一个字节;p被强制类型转换成unsigned int*,整形指针+1就是+4
例3:假设是小端存储
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;
}
结果为:4,2000000
例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;
}
结果为:1
注意逗号表达式,所以该二维数组只初始化为 int a[3][2] = { 1,3,5 }; 实际情况是 int a[3][2] = {{1,3},{5,0},{0,0}};
例5:
int main()
{
int a[5][5];
int(*p)[4];
p = a;
printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
}
结果为:FF FF FF FC(打印的是-4的补码),-4
指针-指针得到的是指针之间元素的个数
例6:
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;
}
结果为:10,5
例7:
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
结果为:at
例8:
int main()
{
char *c[] = {"ENTER","NEW","POINT","FIRST"};
char**cp[] = {c+3,c+2,c+1,c};
char***cpp = cp;
printf("%s\n", **++cpp);
printf("%s\n", *-- *++cpp+3);
printf("%s\n", *cpp[-2]+3);
printf("%s\n", cpp[-1][-1]+1);
return 0;
}
结果为:POINT;ER;ST;EW