指针的基本概念:
- 指针是一个变量,对应内存中唯一的一个地址
- 指针在32位平台下的大小是4字节,在64位平台下是8字节
- 指针是有类型的,指针类型决定该指针的步长,即走一步是多长
- 指针运算:指针-指针表示的是两个指针之间的元素个数
回顾完指针的初阶用法,今天我们要来讲讲指针更高阶的用法
一、字符指针
字符指针就是一个指针,指向一个字符
int main()
{
char ch = 'a';
char* pc = &ch;
printf("%c\n", *pc);//打印a
return 0;
}
还有另一种用法:
int main()
{
char* p = "abcdefg";
printf("%c\n", *p);//打印a
return 0;
}
该怎么理解呢?其实右边的"abcdefg"是一个字符常量,p中存放的是该字符常量首元素的地址。也就是'a'的地址。
来看看一组练习:
int main()
{
char arr1[] = "abcdefg";
char arr2[] = "abcdefg";
char* arr3 = "abcdefg";
char* arr4 = "abcdefg";
if (arr1 == arr2)
printf("arr1和arr2相等\n");
else
printf("arr1和arr2不相等\n");
if(arr3 == arr4)
printf("arr3和arr4相等\n");
else
printf("arr1和arr2不相等\n");
return 0;
}
解答:
二、指针数组
首先,我们得明白指针数组是一个数组,数组中的每个元素是指针类型
int main()
{
int a = 1;
int b = 2;
int c = 3;
int d = 4;
int* p1[] = { &a,&b,&c,&d };//数组中的每个元素是整型指针
char ch1 = 'a';
char ch2 = 'b';
char ch3 = 'c';
char ch4 = 'd';
char* p2[] = { &ch1,&ch2,&ch3,&ch4 };//数组中的每个元素是字符指针
return 0;
}
指针数组可以用来模拟实现一个二维数组,代码如下:
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
int* arr[3] = { arr1,arr2,arr3 };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
打印时,arr[i]相当于每行首元素的地址,通过下标j就可以依次访问每个整型数组中的元素了。
在内存中对应的关系如下:
。
指针数组还有另一种用法:
int main()
{
char* arr[] = { "baiyahua","dashidai","tassel" };
for (int i = 0; i < 3; i++)
{
printf("%s ", arr[i]);
}
return 0;
}
我们得知道,arr中存的是字符'b','d','t'的地址,因此分别打印三个字符长常量。
三、数组指针
3.1数组指针的定义
要同前面的指针数组区别开,数组指针本质上是一个指针,该指针指向一个数组。
int arr[] = { 1,2,3,4,5 };
拿这个代码举例,我们想要将该数组的地址存一个指针当中,得到的那个指针就是一个数组指针。要完成这个操作,需要解决两个问题。
1.怎么取出数组的地址?
2.数组指针的类型改怎么写?
对于第一个问题,我们先来梳理一下数组名的概念:
数组名表示首元素的地址,两个情况除外:
- sizeof(数组名):表示整个数组的大小,单位是字节
- &数组名:表示取出整个数组的地址
到这里,第一个问题就解决了,要得到一个数组的地址,只需要&数组名就行了。
对于第二个问题,C语言规定的语法是这样写的:
int(*p)[5] = &arr;
int *p[5] = &arr;//错误的写法
注意:不能写成下面的形式,因为p首先会跟[]结合,表明这是一个数组,数组中的每个元素是int*类型,p就变成了一个指针数组了。所以一定要将*和p括号起来。
对于上面的形式,我们可以这样理解,p首先跟*结合,表明这是一个指针,在跟[]结合,表明指针指向一个数组,数组中的每个元素是int类型,就是一个数组指针了。
3.2&数组名和数组名
刚刚我们提到,平常的数组名表示的是数组首元素的地址,而&数组名表示的是整个数组的地址,在内存中怎么体现出来呢?
还记得我们最开始讲的指针类型决定该指针的步长,如果是数组指针,那么+1会跳过一个数组。
int main()
{
int arr[5] = { 1,2,3,4,5 };
printf("%p\n", arr);
printf("%p\n", arr + 1);
printf("\n");
printf("%p\n", &arr[0]);
printf("%p\n", &arr[0] + 1);
printf("\n");
printf("%p\n", &arr);
printf("%p\n", &arr + 1);
printf("\n");
return 0;
}
可以看到,虽然&arr和arr,&arr[0]的值相等,当时它们+1的步长不同,这是因为&arr取出数组的地址,+1跳过了整个数组。
3.3数组指针的使用
理解完数组指针,就该谈谈数组指针的使用了,实际写代码中,数组指针一般用在二维数组。
一般的二维数组打印:
void Print(int arr[3][5], int row, int col)
{
int i = row;
for (i = 0; i < row; i++)
{
int j = col;
for (j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
Print(arr,3,5);
return 0;
}
这里的数组传参,形参我们用了数组的形式,实参部分,我们传了二维数组的数组名,数组名是首元素的地址,只不过对于二维数组,数组的首元素是第一行的地址,也就是说,实参传了一个数组的地址,那形参是不是就可以用我们刚刚学的数组指针来接受了。于是就有了第二种写法:
void Print(int(*p)[5] , int row, int col)
{
int i = row;
for (i = 0; i < row; i++)
{
int j = col;
for (j = 0; j < col; j++)
{
printf("%d ", p[i][j]);
}
printf("\n");
}
}
这里来解释一下p[i][j]
p[i]相当于*(p+i),p+i是每行的地址
*(p+i)就是每行的数组名,而数组名又是数组首元素的地址
因此p[i]就相当于每行首元素的地址,通过j就可以依次访问每行的元素。
四、数组传参和指针传参
写代码时,我们难免要将一个数组或指针传参,那么形参部分该如果设计?
判断下面的传参是否正确:
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);
}
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一级指针传参
void Print(int* p, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
}
}
int main()
{
int arr[] = { 1,2,3,4,5,6 };
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr;
Print(p, sz);
return 0;
}
思考:当函数的形参是一级指针的时候,可以接受什么参数?
void test(int* p)
{}
int main()
{
int a = 1;
int* p = &a;
int arr[] = { 1,2,3 };
test(p);//传整型指针
test(&a);//传整型变量的地址
test(arr);//传整型一维数组的数组名
return 0;
}
4.4二级指针传参
void test(int** pp)
{
printf("%d\n", **pp);
}
int main()
{
int a = 1;
int* p = &a;
int** pp = &p;
test(pp);
test(&p);
return 0;
}
思考:当函数的参数是二级指针的时候,可以传什么参数?
void test(int** pp)
{}
int main()
{
int a = 1;
int b = 2;
int c = 3;
int* p = &a;
int** pp = &p;
int* arr[] = { &a,&b,&c };
test(pp);//传二级指针变量
test(&p);//传一级指针变量的地址
test(arr);//传整型指针数组的数组名
return 0;
}
五、函数指针
5.1函数指针的定义
函数指针就是存放一个函数地址的指针,同理解数组指针一样,我们得弄明白
1.怎么得到函数的地址?
2.函数指针变量怎么写?
这里就直接给出结论了,&函数名==函数名,都是取出函数的地址。
函数指针变量的写法:
int test(int x ,int y)
{}
int main()
{
int(*p)(int, int) = test;
int(*p)(int, int) = &test;
//两种写法都可以
return 0;
}
5.2函数指针的使用
int Add(int x ,int y)
{
return x + y;
}
int main()
{
int a = 1;
int b = 2;
int c = Add(1, 2);
int d = (*Add)(1, 2);
printf("c = %d d = %d", c, d);
return 0;
}
对于函数指针,*我们可以加也可以不加,两者是同等的,*并没有实际的作用,但是如果要加*,必须跟p括号起来。
来看看两个有趣的代码:
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);
代码1解读:
该代码我们从0开始,一个整数前面加上括号,第一想到的应该是强制转换。
再看括号里面的部分,void (*)(),是一个函数指针类型,该函数没有参数,返回值是void类型。
加上*,就是引用该函数。
于是,代码的总体意思就是:引用0地址处的函数,该函数没有参数,返回值为void类型
代码2解读:
首先来看signal,后面加上(),容易想到是函数的定义或声明。
再看参数只有类型,没有参数,能确定是函数的声明。
将signal(int,void(*)(int))去掉,余下的void (*)(int)就是函数的返回值类型了。
该代码的意思是:声明一个函数名为signal的函数,该函数的参数类型分别是int和void(*)(int),返回值类型是void(*)(int)
我们还可以对该代码进行一个简化
typedef void (*ptr)(int);
将void(*)(int)类型重命名为ptr,以后我们想用该类型创建变量时,可以直接用ptr。
ptr signal(int, void(*)(int));
六、函数指针数组
函数指针数组,本质上是数组,数组中每个元素都是函数指针。
6.1函数指针数组的定义
int (*arr[])(int, int);
arr首先与[]结合,表明arr是个数组,数组中每个元素的类型是int (*)(int, int)。
6.2函数指针数组的使用
首先我们来模拟一个简单的计算器,可以实现加减乘除法
//计算器version1
void menu()
{
printf("*************************\n");
printf("*** 1.Add 2.Sub ****\n");
printf("*** 3.Mul 4.Div ****\n");
printf("*** 0.Exit ****\n");
printf("*************************\n");
}
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
printf("请输入操作:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
ret = Add(x, y);
printf("%d\n", ret);
break;
case 2:
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
ret = Sub(x, y);
printf("%d\n", ret);
break;
case 3:
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
ret = Mul(x, y);
printf("%d\n", ret);
break;
case 4:
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
ret = Div(x, y);
printf("%d\n", ret);
break;
case 0:printf("退出程序\n");
break;
default:printf("输入错误,请重新输入\n");
break;
}
}while (input);
return 0;
}
该代码虽然能完成我们的要求,但试想,如果我们要更多的运算,一方面我们得增加运算的定义,另一方面我们还要增加case语句的长度,代码就会变得非常长,这时就可以使用函数指针数组来优化我们的代码。
优化后的代码如下:
//计算器version2
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
int(*pf[5])(int, int) = { NULL, Add, Sub, Mul, Div };
//将运算函数的地址存到一个函数指针数组中
//输入数字,通过数组下标执行相应的函数
//为了保证输入的数字和我们想要执行的函数对应
//加上NULL
do
{
menu();
printf("请输入操作:>");
scanf("%d", &input);
if (input >= 1 && input <= 4)
{
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
ret = pf[input](x, y);
printf("%d\n", ret);
}
else if (input == 0)
{
printf("退出程序\n");
}
else
{
printf("输入错误,请重新输入\n");
}
} while (input);
return 0;
}
这时,我们想要增加运算时,只要添加运算的定义,在函数指针数组中增加新增函数的地址,修改一下if条件,而我们的代码长度不变,大大增加了代码的可读性和维护性。
需要注意的是,由于数组是存储一系列相同类型的元素,因此所有函数的参数类型和返回值必须相同才能使用函数指针数组。
七、指向函数指针数组的指针
首先,指向函数指针数组的指针是一个指针,指向一个数组,数组中每个元素的类型都是函数指针类型。
定义:
int (*pf[5])(int, int) = { NULL,Add,Sub,Mul,Div };
int (*(*p)[5])(int, int) = &pf;
//*先跟p结合,表明这是个指针
//往后看到[],表明指针指向一个数组,数组中有5元素
//剩下的int (* )(int, int)就是元素的类型
当然,我们日常写代码时很少用到指向函数指针数组的指针,大家可以做个了解。
八、回调函数
8.1回调函数的定义和使用
回调函数的定义:回调函数不是由函数的实现方直接调用,而是处于某种特定的情境下,由另一个函数通过函数指针的方式调用的。
这里用上面计算器 version1举例,我们发现代码有很多冗余的部分。
这些部分能不能只写成一份?于是就有了我们的计算器version3。
//计算器version3
void Calc(int (*p)(int, int))
{
int x = 0;
int y = 0;
int ret = 0;
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
ret = p(x, y);
printf("%d\n", ret);
}
int main()
{
int input = 0;
do
{
menu();
printf("请输入操作:>");
scanf("%d", &input);
switch (input)
{
case 1:
Calc(Add);
break;
case 2:
Calc(Sub);
break;
case 3:
Calc(Mul);
break;
case 4:
Calc(Div);
break;
case 0:printf("退出程序\n");
break;
default:printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
计算器version3中的Add,Sub,Mul,Div就是回调函数,这些函数不是本身直接调用,而是作为一个参数,传到Calc函数中,由Calc函数调用的。
这里再用库中的qsort函数来帮助大家更好的理解回调函数
8.2qsort的使用
在讲qsort函数之前,简单回顾一下冒泡排序,由于该排序比较简单,就直接给出代码了。
void Bubble_Sort(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz - 1; i++)
{
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
void Print(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
Print(arr, sz);
Bubble_Sort(arr, sz);
Print(arr,sz);
return 0;
}
该代码的缺陷是只能排序整型数据,当我们想要排序结构体类型,字符类型等的数据,它就不能完成要求,但qsort函数是可以做到的。
qsort函数是C语言的库函数,大家可以自行查找具体用法,这里就给出它大致的用法。
void qsort(void* base,
size_t num,
size_t size,
int (*compar)(const void* p1, const void* p2));
//base指向待排序的数组
//num是待排序数组元素的个数
//size是待排序数组一个元素的大小
//compar是一个函数指针,p1和p2是两个待比较的元素
void*类型可以接受任意类型的数据,但不能进行解引用和+-1操作,它没有具体的类型
为什么base,p1和p2要写成void*类型呢?
因为qsort要实现的是对任意类型的数据进行排序,如果是int*,char*等具体类型的指针,就只能接收对应类型的数据,想要排序其他类型的数据,就得改变base,p1和p2的类型,而我们的库函数肯定是不能随便乱动的。
对于qsort,我们只需要实现数据的比较方法函数,再将该函数作为函数指针参数,传到qsort函数中,qsort就会自动为我们排序。
而对于compar函数,我们要完成的功能是:
当p1所指对象<p2所指对象,返回负数
当p1所指对象>p2所指对象,返回正数
当p1所指对象==p2所指对象,返回0
下面使用qsort来排序一个结构体
struct Stu
{
char name[20];
int age;
};
void PirntStruct(const void* arr, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s %d\n", (((struct Stu*)arr)+i)->name, ((struct Stu*)arr+i)->age);
}
printf("\n");
}
//以年龄排序
int compar_by_age(const void* p1, const void* p2)
{
return (((struct Stu*)p1)->age - ((struct Stu*)p2)->age);
}
//以名字排序
int compar_by_name(const void* p1, const void* p2)
{
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
//字符串的比较用strcmp函数,strcmp函数的返回值正好与我们要求的返回值一致
}
int main()
{
struct Stu stu[] = { {"baiyahua",20},{"zhangsan",18},{"lisi",13} };
int sz = sizeof(stu) / sizeof(stu[0]);
printf("排序前:\n");
PirntStruct(stu, sz);
printf("以名字排序后:\n");
qsort(&stu, sz, sizeof(stu[0]), compar_by_name);
PirntStruct(stu, sz);
printf("以年龄排序后:\n");
qsort(&stu, sz, sizeof(stu[0]), compar_by_age);
PirntStruct(stu, sz);
return 0;
}
8.3qsort的模拟实现
知道了qsort的使用方法,我们能不能自己实现一个qsort函数,这里底层用冒泡排序来模拟一个qsort函数。
需要解决的问题:
函数的参数是怎么设计的?这里可以参考库中的qsort函数
怎么比较两个数据?由于需要排序不同类型的数据,就需要不同类型数据的比较方法,我们将方法独立写成一个函数,将该函数的指针传为我们的qsort函数,再我们的qsort函数中调用
怎么交换两个数据?我们将两个数据的内存全部交换相当于交换的两个数据
int compar(const void* p1, const void* p2)
{
return *(int*)p1 - *(int*)p2;
}
void Swap(char* e1, char* e2, int size)
{
int i = 0;
for (i = 0; i < size; i++)
{
char tmp = e1[i];
e1[i] = e2[i];
e2[i] = tmp;
}
}
void my_qsort(void* base, size_t num, size_t size, int compar(const void*,const void*))
{
int i = 0;
for (i = 0; i < num - 1; i++)
{
int j = 0;
for (j = 0; j < num - 1 - i; j++)
{
if (compar((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
{
Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
}
}
}
}
这里解释一下两个地方
想要排序结构体数据时只要给出结构体数据的比较方法即可
九、指针和数组笔试题解析
int main()
{
//一维数组
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a));// 16,sizeof(数组名),计算整个数组的大小
printf("%d\n", sizeof(a + 0));// 4/8,32位机器下4字节,64位机器下8字节
printf("%d\n", sizeof(*a));// 4,sizeof(1),整形数据的大小
printf("%d\n", sizeof(a + 1));// 4/8,地址的大小
printf("%d\n", sizeof(a[1]));// 4,sizeof(2)
printf("%d\n", sizeof(&a));// 4/8,地址的大小
printf("%d\n", sizeof(*&a));// 16,*和&相抵消,相当于sizeof(a)
printf("%d\n", sizeof(&a + 1));// 4/8,地址的大小
printf("%d\n", sizeof(&a[0]));// 4/8,地址的大小
printf("%d\n", sizeof(&a[0] + 1));// 4/8,地址的大小
return 0;
}
//字符数组
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr));//6,计算数组所占空间
printf("%d\n", sizeof(arr + 0));//4/8,地址的大小
printf("%d\n", sizeof(*arr));//1,字符'a'的大小
printf("%d\n", sizeof(arr[1]));//1,字符'b'的大小
printf("%d\n", sizeof(&arr));//4/8,地址的大小
printf("%d\n", sizeof(&arr + 1));//4/8,地址的大小
printf("%d\n", sizeof(&arr[0] + 1));//4/8,地址的大小
printf("%d\n", strlen(arr));//随机值
printf("%d\n", strlen(arr + 0));//随机值
printf("%d\n", strlen(*arr));
//程序报错,'a'的ASCII码值是97,strlen认为97是个地址,而97对于的内存地址不属于我们
printf("%d\n", strlen(arr[1]));//程序报错
printf("%d\n", strlen(&arr));//随机值
printf("%d\n", strlen(&arr + 1));//随机值
printf("%d\n", strlen(&arr[0] + 1));//随机值
return 0;
}
int main()
{
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));// 7
printf("%d\n", sizeof(arr + 0));// 4/8
printf("%d\n", sizeof(*arr));//1
printf("%d\n", sizeof(arr[1]));// 1
printf("%d\n", sizeof(&arr));// 4/8
printf("%d\n", sizeof(&arr + 1));// 4/8
printf("%d\n", sizeof(&arr[0] + 1));// 4/8
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
return 0;
}
int main()
{
char* p = "abcdef";
printf("%d\n", sizeof(p));// 4/8
printf("%d\n", sizeof(p + 1));// 4/8
printf("%d\n", sizeof(*p));// 1
printf("%d\n", sizeof(p[0]));// 1
printf("%d\n", sizeof(&p));// 4/8
printf("%d\n", sizeof(&p + 1));// 4/8
printf("%d\n", sizeof(&p[0] + 1));// 4/8
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
return 0;
}
10.指针笔试题
笔试题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是数组名,既没有sizeof(数组名),也没有&数组名,所以就表示数组首元素地址;
*(a+1)就表示数组第二个元素,也就是2- &a取出整个数组的地址,(&a+1)跳过一个数组,再强转成一个int*类型;因此ptr-1就指向数组倒数第一个元素,*(ptr-1)就是5
笔试题2:
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
p = 0x100000;
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
题目解析:
- p是一个结构体指针,+1跳过一个结构体的大小,因此(p+1)跳过20个字节,转换成16进制就是0x00000014,(p+0x1)就是00100014
- p被强转成无符号长整型,+1就是整型+1,因此((unsigned long)p+0x1)就是00100001
- p被强转成无符号整型指针,+1跳过一个整型指针类型,也就是4字节,((unsigned int*)p+0x1)就是00100004
笔试题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;
}
题目解析:
- (&a+1)跳过数组,再强转成整型;ptr[-1]相当于*(ptr-1),答案是4
- a被强转成整型,+1就是数值+1,再被强转整型指针,这里我们需要画出a数组的详细内存图
*ptr2就是图中的红色部分,但内存中是以小端字节序存储的,拿出来要还原,因此答案就是02000000
笔试题4:
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int* p;
p = a[0];
printf("%d", p[0]);
return 0;
}
题目解析:
- 需要注意,数组的初始化中有(),不要误以为这是在初始化数组,()中是一个表达式,其结果是最后一个表达式的结果,因此(0,1)是1,(2,3)是3,(4,5)是5,实际的数组中的数据 a[0]是数组首行的地址,也是第一行的数组名,数组名表示首元素的地址,a[0]就是第一行第一个元素的地址,p[0]相当于*(p+0);答案是1
笔试题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;
}
题目解析:
- 这题需要我们画出数组内存图
(&p[4][2]-&a[4][2])是一个高地址-低地址,得出的结果是-4;
-4的补码是:11111111 11111111 11111111 11111100,表示成16进制就是FFFFFFFC
以%d打印就是-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;
}
代码解析:
- (ptr1-1)是数组倒数第一个元素的地址,*(ptr1-1)就是10
- *(aa+1)可以看成a[1],是第二行的数组名,也就是第二行首元素的地址,被强转成了int*;因此(ptr2-1)是第一行倒数第一个元素的地址,答案是5
笔试题7:
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;
}
题目解析:
- 内存分布图
需要注意:++会改变cpp的值
因此答案就是POINT,ER,ST,EW
关于指针的内容就讲到这!