一.两段“有趣”的代码
首先,来两个”开胃小菜“,分析一下下面两段代码的含义:
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int,void(*)(int)))(int);
解释1:
在代码1中,先把0这个数字强制类型转成void(*)()这样的函数指针类型。再把转成的函数指针进行解引用,然后再进行函数调用。
(* ( void (*)() ) 0) ();//拆分成这样看更容易理解
解释2:
在代码2中,我们先声明了一个名为signal的函数,这个函数的参数是int和void(*)(int)这样一个函数指针,返回值类型也是一个函数指针。
void ( * signal(int,void(*)(int) ) ) (int);//拆分成这样看更容易理解
二.回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
使用回调函数,模拟实现qsort(采用冒泡的方式)。
#include<stdio.h>
#include<stdlib.h>
int int_cmp(const void * p1, const void * p2){
return (*(int *)p1 > *(int *)p2);
}
void _swap(void *p1, void * p2, int size){
int i = 0;
for (i = 0; i < size; i++){
char tmp = *((char *)p1 + i);
*((char *)p1 + i) = *((char *)p2 + i);
*((char *)p2 + i) = tmp;
}
}
void bubble(void *base, int count, int size, int(*cmp)(void *, void *)){
int i = 0;
int j = 0;
for (i = 0; i< count - 1; i++){
for (j = 0; j<count - i - 1; j++){
if (cmp((char *)base + j*size, (char *)base + (j + 1)*size) > 0){
_swap((char *)base + j*size, (char *)base + (j + 1)*size, size);
}
}
}
}
int main(){
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++){
printf("%d ", arr[i]);
}
printf("\n");
system("pause");
return 0;
}
运行结果:
三.指针和数组笔试题解析
sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。单位:字节bit。
1.一维数组
int arr[] = { 1,2,3,4 };
//arr数组有4个元素,每个元素为int型,故总共大小为4*4个字节。
printf("%d\n", sizeof(arr));
//(arr+0)数组名隐式转换成指针,在32位系统下指针占4个字节。
printf("%d\n", sizeof(arr + 0));
//*arr操作将数组名转换成指针,其指向的是首元素指针,即1个int型,占4个字节。
printf("%d\n", sizeof(*arr));
//(arr+1)操作数组名隐式转换成指针,在32位系统下指针占4个字节。
printf("%d\n", sizeof(arr + 1));
//数组下标为1的元素大小,其实质在求sizeof(int)。占4个字节。
printf("%d\n", sizeof(arr[1]));
//&arr得到的是数组指针int(*)[4],其本质是指针,在32位系统下指针占4个字节。
printf("%d\n", sizeof(&arr));
//&arr得到一个数组指针。*&arr则是进行解引用操作,得到的是一个int[4]数组,故占4*4个字节。
printf("%d\n", sizeof(*&arr));
//arr隐式转换成首元素指针,*arr对指针进行解引用得到int,再进行&*arr取地址操作得到的int*,占4个字节。
printf("%d\n", sizeof(&*arr));
//&arr得到的是数组指针int(*)[4],其本质是指针,&arr+1类型不变,任然是指针,在32位系统下指针占4个字节。
printf("%d\n", sizeof(&arr + 1));
//首元素取地址后是一个int*指针,在32位系统下指针占4个字节。
printf("%d\n", sizeof(&arr[0]));
//首元素取地址后是一个int*指针,+1不影响其类型,在32位系统下指针占4个字节。
printf("%d\n", sizeof(&arr[0] + 1));
2.字符数组
char arr[] = { 'a','b','c','d','e','f' };
//sizeof(char[6]),char类型大小为1个字节,6个元素的大小为6个字节。
printf("%d\n", sizeof(arr));
//sizeof(char*),字符数组隐式转换成字符指针,+多少不影响其类型,在32位系统下指针占4个字节。
printf("%d\n", sizeof(arr + 0));
//sizeof(char),arr获得的是数组首元素的指针,*arr得到的是数组首元素。占1个字节。
printf("%d\n", sizeof(*arr));
//sizeof(char),相当于在求数组下标为1的数组元素的大小。占1个字节。
printf("%d\n", sizeof(arr[1]));
//sizeof(char(*)[6]),&arr得到的是一个数组指针,在32位系统下指针占4个字节。
printf("%d\n", sizeof(&arr));
//sizeof(char[6]),&arr得到一个数组指针,*&arr对该数组指针进行解引用操作,获得一个数组,占6个字节。
printf("%d\n", sizeof(*&arr));
//sizeof(char*),*arr得到数组首元素,&*arr对首元素进行取地址得到一个指针char*,在32位系统下指针占4个字节。
printf("%d\n", sizeof(&*arr));
//sizeof(char*),&arr[1]得到的是char*,+几都不影响其类型,在32位系统下指针占4个字节。
printf("%d\n", sizeof(&arr[1]+1));
//sizeof(char(*)[6]),&arr得到一个数组指针,+几都不影响其类型,在32位系统下指针占4个字节。
printf("%d\n", sizeof(&arr + 1));
//未定义行为,strlen的实现是从头开始遍历数组每个字符,count++,直到遇见“\0”停止计数,
//但是arr是字符数组不是字符串,其后不确定是否存在“\0”,故该操作属于未定义行为。
printf("%d\n", strlen(arr));
//未定义行为,原因同上。
printf("%d\n", strlen(arr + 0));
//*arr操作得到的是首元素,即字符“a”,其类型为char,strlen的参数类型是const char*类型,故该代码编译错误。
printf("%d\n", strlen(*arr));
3.字符串指针
char arr[] = "abcdef";
//此处arr[]是一个字符串,其末尾包含“\0”,故其大小为7个char类型元素,占7个字节。
printf("%d\n", sizeof(arr));
//sizeof(char*),对数组名进行运算就会将数组名隐式转换成指针,在32位系统下指针占4个字节。
printf("%d\n", sizeof(arr + 0));
//sizeof(char),*arr操作获得的是字符串首字符,其大小是1个char类型,占1个字节。
printf("%d\n", sizeof(*arr));
//sizeof(char),arr[1]获得的是下标为1的字符元素,其大小是1个char类型,占1个字节。
printf("%d\n", sizeof(arr[1]));
//sizeof(char(*)[7]),&arr操作获得的是一个数组指针,在32位系统下指针占4个字节。
printf("%d\n", sizeof(&arr));
//sizeof(char[7]),&arr操作获得的是一个数组指针,*&arr操作获得的是一个字符串数组,共占7个字节。
printf("%d\n", sizeof(*&arr));
//sizeof(char*),*arr操作获得字符串首元素,&*arr获得首元素地址,其类型是一个指针,在32位系统下指针占4个字节。
printf("%d\n", sizeof(&*arr));
//sizeof(char*),&arr[0]操作获得的是一个字符指针,在32位系统下指针占4个字节。
printf("%d\n", sizeof(&arr[0] + 1));
//结果:6,strlen计算的是字符串的长度,不计算“\0”。当算占用的内存空间需要包含“\0”。
printf("%d\n", strlen(arr));
//结果:5,arr获得第1个元素地址,arr+1获得第2个元素地址,即从第2个元素开始计算字符串长度。
printf("%d\n", strlen(arr + 1));
//未定义行为,&arr获得的是一个数组指针,&arr+1则是跳过整个数组,即到“\0”后面了。编译失败。
printf("%d\n", strlen(&arr + 1));
//类型不匹配,strlen参数类型是const char*类型,而&arr的类型是char(*)[7],编译会失败。
//但是在实际中,&arr获得的理论上是数组指针,但是实际是arr[0]首元素的地址,其类型是char*,所以实际编译会通过。
printf("%d\n", strlen(&arr));
4.指针数组
int arr[3][4] = { 0 };
//结果:48。sizeof(int[3][4])。arr数组有3*4个元素,每个元素都是in类型,占3*4*4个字节。
printf("%d\n", sizeof(arr));
//结果:4。sizeof(int)。arr[0][0]得到二维数组的第一个元素,类型为int。
printf("%d\n", sizeof(arr[0][0]));
//结果:16。sizeof(int[4])。arr[0]获得的是长度为4的一维数组,其中每个元素
printf("%d\n", sizeof(arr[0]));
//结果:4。sizeof(int*)。arr[0] + 1将数组隐式转换成指针,在32位系统下指针占4个字节。
printf("%d\n", sizeof(arr[0] + 1));
//结果:48。sizeof(int)。arr[0]获得一个数组,由于前面是运用了*,
//故该处数组隐式转换成指针,*为解引用,又变成了数组元素。
//结果:16。sizeof(int[4])。arr从二维数组隐式转换成指针int(*)[4],在对其解引用操作又将其变成一个数组int[4]。
printf("%d\n", sizeof(*arr));
//结果:4。sizeof(int*)。*arr先将arr隐式转换成一个数组指针int(*)[4],然后解引用成数组int[4],
//*arr+1又将其转换成指针,在32位系统下指针占4个字节。
printf("%d\n", sizeof(*arr + 1));
//结果:16。sizeof(int[4])。arr+1将数组隐式转换成数组指针nt(*)[4],在对其解引用操作又将其变成一个数组int[4]。
printf("%d\n", sizeof(*(arr + 1)));
//结果:4。sizeof(int(*)[4])。arr[0]得到一个数组,&arr[0]得到一个数组指针,
//对指针进行运算不改变其类型,在32位系统下指针占4个字节。
printf("%d\n", sizeof(&arr[0] + 1));
//结果:4。sizeof(int(*)[4])。arr[0]获得一个数组,&arr[4]获得一个int(*)[4]数组指针
//运算无法改变指针类型。在32位系统下指针占4个字节。
printf("%d\n", sizeof(&arr[0] + 1));
//结果:4。sizeof(int(*)[4])。int[3][4]是一个长度为3的二维数组,其下标取值只能是0,1,2
//所以取值10已经超过了下标,但是代码可以运行,叫做编译器求值,不是未定义行为,
//代码编译完了之后生成的二进制指令相当于printf("%d\n",4),在运行过程中不涉及内存访问,更没有数组下标越界。
printf("%d\n", sizeof(arr[10]));
四.代码分析笔试题
笔试题1:
int main(){
int arr[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&arr + 1);
printf( "%d,%d", *(arr + 1), *(ptr - 1));
return 0;
}
运行结果:
解析:
arr是一个数组,arr+1将数组隐式准换成指针,并且指向数组下标为1的元素,*(arr+1)对其进行解引用,获得数组下标为1的元素,即2。
&arr获得的是一个数组指针,&arr+1则是跳过了整个数组,指向了数组后面的位置,接着又用(int * ) 对数组指针类型进行类型强制转换成(int *)类型,此时ptr指向的位置是整个数组末尾,(ptr-1)则指向了元素5这个位置, *(ptr-1)解引用操作获得元素5。
笔试题2:
//这里的结构体Test类型的变量大小是20个字节。
struct Test{
int Num;
char *pcName;
shortsDate;
char cha[2];
shortsBa[4];
}*p;
假设p 的值为0x100000。 如下表表达式的值分别为多少?
p + 0x1 = 0x___ ?
解析:
*p=>(struct Test *),这个结构体指针的地址是0x100000,(p+0X1)是指在指针的基础上进行+1则要跳过整个结构体,也就是要在该结构体指针的地址上再加上20个字节,此出需要注意的是20是十进制的表示,而我们打印的时候需要输出16进制的表示,所以先要将十进制20转换成十六进制14表示,然后进行相加,得到最后结果:0X100014。
(unsigned long)p + 0x1 = 0x___ ?
解析:
这个结构体指针的地址是0x100000,(unsigned long)p对其进行强制类型转换,将其从指针类型转换成长整型,(unsigned long)p + 0X1就是在长整型基础上+1,得到最后结果:0X100001。
(unsigned int*)p + 0x1 = 0x___ ?
解析:
这个结构体指针的地址是0x100000,(unsigned int*)p 将其强制类型转换,转换成int *指针,接下来,(unsigned int *)p + 0x1进行的还是指针运算,表示跳过一个int型元素,即在原始地址上+4。得到最后结果:0X100004。
笔试题3:
int main(){
int arr[4] = { 1, 2, 3, 4 };
int *ptr1 = (int *)(&arr + 1);
int *ptr2 = (int *)((int)arr + 1);
printf( "%x,%x", ptr1[-1], *ptr2);
return 0;
}
解析:
&arr将数组阴识类型转换成数组指针,(&arr+1)跳过整个数组,(int *)(&arr + 1)强制类型转换,将指针强制转换成(int *)指针,所以,ptr1是一个int *型指针,指向的位置是在arr数组后面位置,现在对其进行ptr1[-1],其含义是对ptr1先进行-1左移操作后再解引用,结果是:4。
(int)arr将数组强制类型转换成一个整数,(int)arr+1则是按照整数进行加法运算,此时内存地址按照int类型往后移动一位,(int *)((int)arr + 1),最后又进行了强制类型转换,将整数转换成了int *指针类型,*ptr2是对该指针进行了解引用,结果为:0X2000000。