谈谈C语言中不那么常见的指针类型谈谈C语言指针的进阶用法 指针的基础概念: 指针就是存放地址的变量, 大小随着系统(32
谈谈C语言指针的进阶用法
指针的基础概念:
- 指针就是存放地址的变量, 大小随着系统(32位/64位 地址线)改变,为4/8字节
- 指针有类型之分, 大小都一样, 决定了指针与整数运算时的步长和指针解引用能访问空间的大小
- 指针+指针 没有意义 指针-指针=整数(两者之间的元素数量) 指针+整数=指针 指针-整数=指针
// 指针+-指针 = 指针
char *p = (char*)0x1000000000000000;
char *s = (char*)0x100000000000000F;
printf("%ld",s-p);
输出结果: 15
// 指针+-整数 结果为指针(移动了整数*指针的步长)
int *p = (int*)0x1000000000000000;
int *s = p+4;
printf("%p\n",s);
输出结果: 0x1000000000000010
字符指针
- 字符指针指向常量字符串
char str1[] = "hello";
char str2[] = "hello";
const char* str3 = "hello";
const char* str4 = "hello";
if(str1 == str2){
printf("str1 and str2 are the same\n");
}
else{
printf("str1 and str2 are not same\n");
}
if(str3 == str4)
{
printf("str3 and str4 are the same\n");
}
else
{
printf("str3 and str4 are not same\n");
}
输出结果为:
str1 and str2 are not same
str3 and str4 are the same
由该程序的结果可以看出:
-
常量字符串指针 本质是把字符串的首字符地址放到了指针中
-
这里的str3和str4指向的是同一个常量字符串 C/C++会把常量字符串存储到一个单独的内存区域, 几个指针指向同一个字符串的时候,实际是指向同一块内存
-
如果是直接用字符串初始化字符数组, 那么其实是新开辟一块内存区域
指针数组
就是存放指针的数组 形式如:
char* arr[100];//存放100个指向char变量的指针的数组
数组指针
是指向数组的指针 如int* 指向int类型的变量,数组指针是指向数组类型的变量(也就是数组)
形式: (容易与指针数组混淆)
int *p[10];//是一个指针数组 存放了10个指向int类型变量的指针
int (*p)[10];//数组指针 指向了一个10个int大小的数组
关于形式的解释: p先会与 * 结合, 说明p是一个指针变量,然后指针指向一个大小为10个整型的数组, 所以p是一个指向数组的数组指针( [] 的优先级高于* 号)
数组名 和 &数组名的区别
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
printf("%p\n",arr);
printf("%p\n",&arr);
printf("%p\n",arr+1);
printf("%p\n",&arr+1);
输出结果为:
0x16b50a920
0x16b50a920
0x16b50a924
0x16b50a948
两者值是一样的 但其实代表的意义不一样
实际上, &arr表示数组的地址, 而不是数组首元素的地址, 步长为数组的大小而决定, arr表示的是数组首元素的地址, 步长依然为数组元素的类型而决定
&arr的类型为: int( * )[10] &arr+1与&arr 相差4(int)* 10(数组大小) = 40字节(步长为40字节)
数组指针的使用
可以使用在二维数组传参中
void print_arr1(int arr[3][5],int row,int col)
{
int i=0;
for(int i=0;i<row;i++)
{
for(int j=0;j<col;j++)
{
printf("%2d ",arr[i][j]);
}
printf("\n");
}
}
void print_arr2(int (*arr)[5],int row,int col)
{
int i = 0;
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf("%2d ",arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = {
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
};
print_arr1(arr,3,5);
print_arr2(arr, 3, 5);
return 0;
}
输出结果:
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
两个函数输出结果是一样的
这里第二个函数int (* arr)[5]形参, 其实本质上就是int(* )[5]数组类型的地址, 把二维数组当作一维数组去理解. 那么就是第一行一维数组的地址, 每次+1, 那么就会指向不同行的一维数组
所以二维数组也可以用这种形式表达出来, 因为我们要确定每一行是什么数组类型(也就是数组的基本元素是什么类型,数组本身多长), 才能够准确的指向每一行的一维数组, 所以在传参的时候列的数量不可省略
这里用三种颜色表明了三种形式传参的区别
二维数组传参,函数形参的设计只能省略第一个[]的数字。 因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素,才能够计算出每一行首元素的地址
函数指针
void test()
{
printf("hello,world!\n");
}
int main()
{
printf("%p\n",test);
printf("%p\n",&test);
return 0;
}
输出:
0x102833f0c
0x102833f0c
输出两个函数的地址 所以函数名也就是一个指针 并且函数名和&函数名效果相同
定义变量存储函数指针
printf("%p\n", test);
printf("%p\n", &test);
void (*p)() = test;
printf("%p\n",p);
输出
0x1004abef0
0x1004abef0
0x1004abef0
定义形式: 返回类型(* 指针名)(参数类型列表) 函数的类型为: 返回类型(* )(参数类型列表) 比如函数
int add(int a,int b)
{
return a+b;
}
函数指针的定义为
int(* p)(int,int) = &add;
函数类型就是: int(* )(int,int)
判断:
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);
- 代码1: 是一个函数调用,空参数,外层* 是将指针指向的函数取到调用,调用的函数是一个返回类型为空,无参数的函数,(void(* )())0是将0x00000000强制转换为一个函数的地址然后调用这个函数
- 代码2: 声明一个函数,简化用typedef可以转换为
typedef pf void(*)(int)
pf signal(int,pf);
函数指针的用途——转换表
如一组函数,具有相同的返回值和参数类型列表, 那么可以定义一个函数指针数组保存这些函数的地址
结合switch语句, 可以起到选择然后直接通过函数指针调用函数的效果, 代码简洁
#include <stdio.h>
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);
printf( "ret = %d\n", ret);
}
else
printf( "输入有误\n" );
}
return 0;
}
回调函数
典型: qsort函数,将比较函数作为参数传入qsort
自定义qsort,感受回调函数在定义好的函数代码中起作用的过程
void Swap(char* buf1, char* buf2, int size)
{
//看成是字节数组进行交换即可
for (int i = 0; i < size; i++)
{
char tmp = buf2[i];
buf2[i] = buf1[i];
buf1[i] = tmp;
}
}
//使用冒泡排序的思想 实现一个类似qsort的函数
void my_sort(void* base, size_t num, size_t size, int(*cmp)(const void*, const void*))
{
//难点 怎么将void*指针根据传入的类型大小转化为自己想要的类型 --> 转为char*类型 变为步长1个字节的指针
// 比较方式 在调用者传入的具体实现中进行取值比较 现在传入指针即可
//交换的方式也有差异 看成是字节数组进行交换即可
for (int i = 0; i < num - 1; i++)
{
for (int j = 0; j < num - 1 - i; j++)
{
if (cmp((char*)base+j*size,(char*)base+(j+1)*size)>0)//两个元素比较 需要将arr[j]与arr[j+1]的地址传给cmp
{
//交换
Swap((char*)base+j*size, (char*)base + (j + 1) * size,size);
}
}
}
}
注意,因为排序是自定义排序的规则,在写排序函数的时候作者不会知道具体需要排序什么类型的数,所以这里要使用void*
void* 只有表示地址的功能无法进行整数加减法, 具体解引用的时候要使用到传入到函数中的参数类型大小, 依照此大小进行解引用, 如果要进行交换,就只能逐个字节进行交换(与大小端存储无关)
总结: 与Java的函数式接口类似
一些指针与数组的辨析题
数组名的意义:
- sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
- &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
- 除此之外所有的数组名都表示首元素的地址。
//这里假设指针大小是8字节
//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a)); //整个数组的大小 4*4 = 16字节
printf("%d\n",sizeof(a+0)); //8
printf("%d\n",sizeof(*a)); //4 int
printf("%d\n",sizeof(a+1)); //8
printf("%d\n",sizeof(a[1])); //4
printf("%d\n",sizeof(&a)); //整个数组的地址 8
printf("%d\n",sizeof(*&a)); //16
printf("%d\n",sizeof(&a+1)); //8
printf("%d\n",sizeof(&a[0])); //8
printf("%d\n",sizeof(&a[0]+1)); //8
//字符数组 strlen传入char*之后 会一直往后找'\0'标志字符串的结束
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
printf("%d\n", strlen(arr)); //random
printf("%d\n", strlen(arr+0)); //random
printf("%d\n", strlen(*arr)); //error
printf("%d\n", strlen(arr[1])); //error
printf("%d\n", strlen(&arr)); //error
printf("%d\n", strlen(&arr+1)); //error
printf("%d\n", strlen(&arr[0]+1)); //random
char arr[] = "abcdef";
printf("%d\n", sizeof(arr)); //7
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
printf("%d\n", strlen(arr)); //6
printf("%d\n", strlen(arr+0)); //6
printf("%d\n", strlen(*arr)); //error
printf("%d\n", strlen(arr[1])); //error
printf("%d\n", strlen(&arr)); //error
printf("%d\n", strlen(&arr+1)); //error
printf("%d\n", strlen(&arr[0]+1)); //5
char *p = "abcdef"; //strlen函数只能传入char*类型的参数
printf("%d\n", sizeof(p)); //8
printf("%d\n", sizeof(p+1)); //8
printf("%d\n", sizeof(*p)); //1
printf("%d\n", sizeof(p[0])); //1
printf("%d\n", sizeof(&p)); //8
printf("%d\n", sizeof(&p+1)); //8
printf("%d\n", sizeof(&p[0]+1)); //8
printf("%d\n", strlen(p)); //6
printf("%d\n", strlen(p+1)); //5
printf("%d\n", strlen(*p)); //error
printf("%d\n", strlen(p[0])); //error
printf("%d\n", strlen(&p)); //error
printf("%d\n", strlen(&p+1)); //error
printf("%d\n", strlen(&p[0]+1)); //5
//二维数组 此时a[0]相当于是一个一维数组的数组名 所以*(a[0]+1)表示第二行的第一个元素
int a[3][4] = {0};
printf("%d\n", sizeof(a)); // 12*4 = 48
printf("%d\n", sizeof(a[0][0])); // 4
printf("%d\n", sizeof(a[0])); // 16
printf("%d\n", sizeof(a[0] + 1)); // 16
printf("%d\n", sizeof(*(a[0] + 1))); //4
printf("%d\n", sizeof(a + 1)); //8
printf("%d\n", sizeof(*(a + 1))); //16
printf("%d\n", sizeof(&a[0] + 1)); //8
printf("%d\n", sizeof(*(&a[0] + 1))); //16
printf("%d\n", sizeof(*a)); //16
printf("%d\n", sizeof(a[3])); //16
指针相关问题
- 问题一:
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);// &a整个数组,+1指向相邻下一个数组,
//强制转换为只指向一个int,-1之后就是数组末尾的元素,*(a+1)就是a[1]
printf( "%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
//程序的结果是什么? 2 5
- 问题二:
struct Test
{
int Num;//4
char *pcName;//8
short sDate;//2
char cha[2];//2
short sBa[4];//8
}*p //经过数据对齐之后是20个字节(32位机器,编译带优化选项)
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
printf("%p\n", p + 0x1); //指针+整数 0x100014
printf("%p\n", (unsigned long)p + 0x1); //0x100001
printf("%p\n", (unsigned int*)p + 0x1); //0x100004
return 0;
}
- 问题三:
int main()
{
int a[4] = { 1, 2, 3, 4 };
int *ptr1 = (int *)(&a + 1); //末尾元素的下一个元素
int *ptr2 = (int *)((int)a + 1); //注意是小端法存储 第二个字节 01 00 00 00 02 00 00 00
// 指向的对象是 00 00 00 02 --》0x2000000
printf( "%x,%x", ptr1[-1], *ptr2);//0x4 0x2000000
return 0;
}
- 问题四:
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) }; //括号表达式,(x,y)先计算x再计算y,最后的值是y
int *p;
p = a[0];//1 3
printf( "%d", p[0]);//1
return 0;
}
- 问题五:
int main()
{
int a[5][5]; //本质上其实是int(*)[5]类型
int(*p)[4]; /int(*)[4]类型的指针
p = a; //强制转换 变为一行四列
printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
//4*4+2-5*4+2 = -4
//%p 最终要打印的是一个无符号整数 所以最终为0xFFFFFFFC -4
//注意: 指针-指针 = 整数(表示两者之间的元素数量差) 指针+指针没有意义
return 0;
}
- 问题六:
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)); //10 5
return 0;
}
- 问题七:
int main()
{
char *a[] = {"work","at","alibaba"};//分别指向w,a,a
char**pa = a;
pa++;//第二个 此时pa也变了
printf("%s\n", *pa);//at
return 0;
}
- 问题八:
int main()
{
char *c[] = {"ENTER","NEW","POINT","FIRST"};//分别指向E,N,P,F
char**cp[] = {c+3,c+2,c+1,c};//CP[0] CP[1] CP[2] CP[3]
char***cpp = cp;
printf("%s\n", **++cpp); //先++ *cpp = c+2 *(c+2) = POINT
printf("%s\n", *--*++cpp+3); //*cpp = c+1 --c+1 = c *c+3 = ER
printf("%s\n", *cpp[-2]+3); //**(cpp-2) = *(c+3) = P +3 ST
printf("%s\n", cpp[-1][-1]+1); //EW
return 0;
}
转载自:https://juejin.cn/post/7400942119080968244