目录
1. 字符指针
2. 数组指针 与 指针数组
4. 数组传参和指针传参
一维数组传参
二维数组传参
一级指针传参
二级指针传参
5. 函数指针
6. 函数指针数组
7. 指向函数指针数组的指针
8. 回调函数
9. 指针和数组面试题的解析
数组笔试题
指针笔试题
1. 字符指针
从例题看起—>
#includeint main() { char str1[] = "hello!"; char str2[] = "hello!"; const char *str3 = "hello!"; const char *str4 = "hello!"; if(str1 ==str2) printf("str1 and str2 are samen"); else printf("str1 and str2 are not samen"); if(str3 ==str4) printf("str3 and str4 are samen"); else printf("str3 and str4 are not samen"); return 0; }
结果是,指针变量str1和str2不同,str3和str4相同。
原因是str1和str2数组是在不同内存空间上开辟空间,而str3和str4指向的字符串是相同的常量字符串,C/C++会把常量字符串存储到单独的一个内存区域(静态区),当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存区域。
2. 数组指针 与 指针数组
数组指针是指向数组的指针。
可以这么定义吗?
int * p1 [ 10 ]; int ( * p2 )[ 10 ]; //p1, p2 分别是什么? 需要注意的是操作符的优先级:[ ]比*的优先级高 故对于p1而言,p1先与[]结合表明p1是一个10个元素的数组,数组的每个元素类型是int *,故这是在定义一个指针数组。 对于p2而言,p2先与*结合,表明p2是一个指针,指向的一个10个元素的数组,数组的每个元素类型是int ,故这是一个数组指针。(注意:[10]内的10不可省略,否则指针的步长是不可知的) 实例1(数组指针的使用):#include//例如要打印一个二维数组的每个元素,可以这么写 void print_arr1(int arr[3][5], int row, int col) { int i = 0; for(i=0; i |
实例2:理解如下写法
int arr [ 5 ]; //定义一个数组 int * parr1 [ 10 ]; //定义一个指针数组 int ( * parr2 )[ 10 ]; //定义一个数组指针 int ( * parr3 [ 10 ])[ 5 ]; //定义一个数组指针数组 //parr3先与[10]结合表明是一个数组,数组的每个元素类型是int(*)[5]即数组指针,故定义的是一个有10个元素的数组,数组的每个元素类型都是指向5个整型元素数组的指针(即数组指针)。4. 数组传参和指针传参
一维数组传参
#include1.void test(int arr[])//ok? {} /正确,这种写法与void test(int *arr){}等价, /传参的本质是传一个指针,传数组名的时候传的是首元素地址。 2.void test(int arr[10])//ok? {} /正确,与上一种相同,[]内无具体要求,因为传的是指向整型元素的一级指针 3.void test(int *arr)//ok? {} /同上两种 -------------------------------------------------------------- 4.void test2(int *arr[20])//ok? {} /传数组名的时候,传的是指针数组的首元素地址,与第一种不同的是数组元素类型不是int而是int* 5.void test2(int **arr)//ok? {} /与上一种写法相同,因为指针数组首元素类型是指针,故首元素地址类型是int** int main() { int arr[10] = {0}; int *arr2[20] = {0}; test(arr); test2(arr2); } 二维数组传参
/首先二维数组的数组名是首行的地址,也即是一个数组的地址/ 1.void test(int arr[3][5])//ok? {} /正确 2.void test(int arr[][])//ok? {} /错误,二维数组传参,列数不可省略 3.void test(int arr[][5])//ok? {} /正确 //二维数组传参,函数形参的设计只能省略第一个[]的数字。 //因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。(也决定着数组指针的步长,即一步跳过几个元素的数组) 4.void test(int *arr)//ok? {} /传一级指针,错误 5.void test(int* arr[5])//ok? {} /错误的,这个写法与7.等价,使用时传的应该是指针数组的地址即二级指针 6.void test(int (*arr)[5])//ok? {} /传数组指针,正确,[5]中5当准确填写! 7.void test(int **arr)//ok? {} //传二级指针,错误 int main() { int arr[3][5] = {0}; test(arr);/传参本质是传数组指针 }一级指针传参
#includevoid print(int *p, int sz) { int i = 0; for(i=0; i 二级指针传参
void test(char **p) { } int main() { char c = 'b'; char*pc = &c;/pc是一级指针 char**ppc = &pc;/ppc是二级指针 char* arr[10];/arr是指针数组的数组名,是首元素地址,也是二级指针 test(&pc); test(ppc); test(arr);//Ok?, /以上调用二级指针写法都正确. return 0; }5. 函数指针
顾名思义是函数的地址:
#includevoid test() { printf("hehen"); } int main() { printf("%pn", test); printf("%pn", &test); return 0; } 代码结果是两者地址相同,在C语言中函数名和&函数名区别不大,函数名就表示了函数的地址。
要想保存函数的地址,应用函数指针:
例如:
void test () { printf ( "hehen" ); } 要保存test函数的地址应当如此声明: void ( * pfun1 )();//pfun1先与*结合,表明这是一个指针,pfun1右边的*()表明指向的函数的参数,左边void 表明指向的函数的返回类型 void * pfun2 ();//错误的声明,pfun2与()先结合表明pfun2是一个函数,void*是函数的返回类型。 深刻理解函数指针: // 代码 1 ( * ( void ( * )() ) 0 )(); // void (*)()是一个函数指针类型,加上()放在0前面,表示将0强制类型转换为函数指针类型的变量,然后再与*结合,解引用函数指针变为函数,最右边()是函数的形参 // 代码 2 void ( * signal ( int , void ( * )( int )))( int ); //signal先与()结合表明signal是一个函数,函数的两个参数类型分别为整型int和函数指针 void(*)(),函数的返回类型是函数指针void(*)(int)。 //代码二略显可读性差,可以优化一下 typedef void ( * pfun_t )( int );//将函数指针类型重命名为pfun_t,pfun_t应当放在括号内,这是语法规定 pfun_t signal ( int , pfun_t );//于是代码二可以这么写,较为易读。6. 函数指针数组
顾名思义是存放函数指针的数组,看似复杂,其实是个数组。
例子分析: int ( * parr1 [ 10 ])();//parr1先与[10]结合,表明parr1是一个数组,数组每个元素类型是 函数指针类型 int(*)(),故这个数组是函数指针数组 int * parr2 [ 10 ]();//错误的声明,编译器会报错数组的元素类型不能是函数或抽象类类型 int ( * )() parr3 [ 10 ];//错误的声明,语法错误,parr3[10]当放在*右边函数指针数组用途:转移表(存放多个函数)
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表,便于函数的调用
7. 指向函数指针数组的指针
顾名思义的6.函数指针数组的地址。
理解如下代码:
void test(const char* str) { printf("%sn", str); } int main() { /函数指针pfun void (*pfun)(const char*) = test; /函数指针的数组pfunArr void (*pfunArr[5])(const char* str); pfunArr[0] = test; /指向函数指针数组pfunArr的指针ppfunArr void (*(*ppfunArr)[5])(const char*) = &pfunArr;/注意()的使用,使用错误时意义大不相同。 /理解方式:ppfunArr先与*结合表明ppfunArr是一个指针,指针指向5个元素的数组,数组每个元素的类型是 /函数指针void(*)(const char*) return 0; }8. 回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这个被调用的函数是回调函数。 一般而言,我们不能修改库函数的实现,也就是说不能通过修改库函数让库函数调用普通函数那样实现,那我们就只能通过传入不同的回调函数去实现我们想实现的功能。 例如:调用库函数qsort去排序不同类型的数据时,使用者要自己实现一个回调函数 首先了解qsort: void qsort( void *base, size_t number, size_t width, int ( *compare )(const void *elem1, const void *elem2) ); qsort的四个参数分别为排序数据的首地址,排序的元素个数,元素大小,比较函数指针(elem1是元素1的地址,elem2是元素2的地址,当元素1小于元素2比较函数返回负数可进行升序排序)#include//qosrt函数的使用者得实现一个比较函数 int int_cmp(const void * p1, const void * p2) { return (*( int *)p1 - *(int *) p2); } int main() { int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 }; int i = 0; qsort(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"); return 0; } 写一个冒泡排序,可以排序任意类型的数据:
#includeint 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 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"); return 0; } 9. 指针和数组面试题的解析
假设是在32位平台下
数组笔试题
1.一维数组 数组名是首元素地址,除了两个例外 (1)sizeof(数组名),求整个数组大小 (2)&数组名 得到数组地址(数组指针) int a[] = {1,2,3,4}; printf("%dn",sizeof(a)); 16 数组大小 printf("%dn",sizeof(a+0)); 4 首元素地址 printf("%dn",sizeof(*a)); 4 首元素 printf("%dn",sizeof(a+1)); 4 第二个元素地址 printf("%dn",sizeof(a[1])); 4 第二个元素 printf("%dn",sizeof(&a)); 4 数组指针 printf("%dn",sizeof(*&a)); 16 数组大小 printf("%dn",sizeof(&a+1)); 4 数组指针 printf("%dn",sizeof(&a[0])); 4 第一个元素地址 printf("%dn",sizeof(&a[0]+1)); 4 第二个元素地址strlen是求字符串长度函数,从给定的地址往后计数直到遇到 为止,放回 之前的字符个数 2.字符数组 char arr[] = {'a','b','c','d','e','f'};数组初始化为6个元素 printf("%dn", sizeof(arr)); 6 数组大小 printf("%dn", sizeof(arr+0)); 4 数组首元素地址 printf("%dn", sizeof(*arr)); 1 数组首元素 printf("%dn", sizeof(arr[1])); 1 数组第二个元素 printf("%dn", sizeof(&arr)); 4 数组指针 printf("%dn", sizeof(&arr+1)); 4 数组指针 printf("%dn", sizeof(&arr[0]+1)); 4 第二个元素地址 printf("%dn", strlen(arr)); 随机值>=6 printf("%dn", strlen(arr+0)); 随机值>=6 printf("%dn", strlen(*arr)); 将*arr也即a的ASCII码值作为地址开始访问,非法访问内存 printf("%dn", strlen(arr[1])); 非法访问内存 printf("%dn", strlen(&arr)); 随机值>=6 printf("%dn", strlen(&arr+1)); 跳过整个数组开始求长度,比上行结果少6的随机值 printf("%dn", strlen(&arr[0]+1)); 随机值,从b的地址开始求字符串长度 ----------------------------------- char arr[] = "abcdef";数组初始化为7个元素含 printf("%dn", sizeof(arr)); 7 数组大小 printf("%dn", sizeof(arr+0)); 4 数组首元素地址 printf("%dn", sizeof(*arr)); 1 首元素 printf("%dn", sizeof(arr[1])); 1 第二个元素 printf("%dn", sizeof(&arr)); 4 数组指针 printf("%dn", sizeof(&arr+1)); 4 数组指针 printf("%dn", sizeof(&arr[0]+1)); 4 第二个元素地址 printf("%dn", strlen(arr)); 6 字符串长度 printf("%dn", strlen(arr+0)); 6 字符串长度 printf("%dn", strlen(*arr)); 将a的ASCII码值作为地址,非法访问内存 printf("%dn", strlen(arr[1])); 非法访问内存 printf("%dn", strlen(&arr)); 6 字符串长度 printf("%dn", strlen(&arr+1)); 随机值,从 后开始求字符串长度 printf("%dn", strlen(&arr[0]+1)); 5 从b开始求字符串长度 -------------------------------- char *p = "abcdef";p是字符指针指向常量字符串 printf("%dn", sizeof(p)); 4 首元素地址 printf("%dn", sizeof(p+1)); 4 第二个元素地址 printf("%dn", sizeof(*p)); 1 首元素 printf("%dn", sizeof(p[0])); 1 首元素 printf("%dn", sizeof(&p)); 4 二级指针(p的地址) printf("%dn", sizeof(&p+1)); 4 指针 printf("%dn", sizeof(&p[0]+1)); 4 b的地址 printf("%dn", strlen(p)); 6 字符串长度 printf("%dn", strlen(p+1)); 5 从b开始求字符串长度 printf("%dn", strlen(*p)); 非法访问内存 printf("%dn", strlen(p[0])); 非法访问内存 printf("%dn", strlen(&p)); 随机值,从p的地址开始求字符串长度 printf("%dn", strlen(&p+1)); 随机值,从p的地址跳过4个字节(char*类型)开始求字符串长度 printf("%dn", strlen(&p[0]+1)); 5 从b开始求字符串长度 3.二维数组 二维数组看成多个一维数组为元素组成的数组,二维数组的数组名是首行数组的地址 int a[3][4] = {0}; printf("%dn",sizeof(a)); 48 整个二维数组 printf("%dn",sizeof(a[0][0])); 4 二维数组首行的首元素 printf("%dn",sizeof(a[0])); 16 相当于首行数组数组名单独在sizeof内,求的是首行数组大小 printf("%dn",sizeof(a[0]+1)); 4 a[0]是首行首元素地址,加一跳过一个元素到第二个元素的地址 printf("%dn",sizeof(*(a[0]+1))); 4 首行第二个元素 printf("%dn",sizeof(a+1)); 4 第二行数组的地址 printf("%dn",sizeof(*(a+1))); 16 第二行数组大小 printf("%dn",sizeof(&a[0]+1)); 4 第二行数组地址 printf("%dn",sizeof(*(&a[0]+1))); 16 第二行数组大小 printf("%dn",sizeof(*a)); 16 第一行数组大小 printf("%dn",sizeof(a[3])); 16 理解为*(a+3),a是数组指针,加一跳过一行, 解引用是那行数组的数组名,求的是那行数组的大小,sizeof只求大小不访问该内存空间指针笔试题
笔试题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+1是第二个元素的地址,*解引用得第二个元素2 &a是数组地址,加一跳过整个数组,类型转换为int*,ptr-1是元素5的地址,*解引用是5笔试题2: 结构体的大小是20个字节 struct Test { int Num; 4 char *pcName;4 short sDate; 2 char cha[2]; 2 short sBa[4]; 8 }*p; //假设p 的值为0x100000。 如下表表达式的值分别为多少? //已知,结构体Test类型的变量大小是20个字节 int main() { printf("%pn", p + 0x1);p的类型是结构体指针,加一跳过一个结构体大小20个字节, 16进制表示为0x100014 printf("%pn", (unsigned long)p + 0x1);p被强制转换为长整形,加一是整数加减为0x100001 printf("%pn", (unsigned int*)p + 0x1);p被强制类型转换为整形指针,加一跳过一个整形, 故为0x100004 return 0; }笔试题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);%x是以16进制打印 return 0; } &a是取出数组地址,加一跳过整个数组大小,强制类型转换为int*类型, ptr1[-1]理解为*(ptr-1),为4 a是首元素地址,类型转换为整形,加一跳过一个字节,即指向01后面的0的位置01 00 00 00 02, 解引用4个字节为02000000,故打印2000000笔试题4: #includeint main() { int a[3][2] = { (0, 1), (2, 3), (4, 5) };注意逗号表达式,依次计算各个表达式, 但以最后一个表达式返回,故二维数组内容实际上是1,3, 5,0, 0,0 int *p; p = a[0]; printf( "%d", p[0]); return 0; } a[0]是首行数组的数组名,故p是元素1的地址,p[0]与*(p+0)等价,故打印1 笔试题5: int main() { int a[5][5]; int(*p)[4]; p = a; printf( "%p,%dn", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]); return 0; } 注意:指针相减得到的是指针间的元素个数。 p是数组指针,指向二维数组首行,类型是int(*)[4],加一将跳过(4个整型)16个字节 a也是数组指针,但类型是int (*)[5],加一跳过(5个整型)20个字节 假设数组元素 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 &p[4][2]指向的是19,&a[4][2]指向的是23,两者相减以%p打印, 直接打印-4的补码FFFFFFFC(相差4个元素),%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; } &aa是整个二维数组的地址,加一跳过整个二维数组,指向元素10的后面, 类型转为int*,减一指向10,故打印10 aa是首行数组地址,加一跳过整行指向6,解引用得第二行数组数组名,类型转换为int* 减一指向5,故打印5笔试题7: #includeint main() { char *a[] = {"work","at","alibaba"}; char**pa = a; pa++; printf("%sn", *pa); return 0; } 数组a是指针数组,数组元素是三个字符指针,分别指向三个字符串 a是数组名,是首元素地址,即指针地址是二级指针的类型 故pa是指针,指向的元素类型是char*类型,加一跳过4个字节(指针大小) 原本pa是指向 指向work字符串的指针,现在指向 指向at字符串的指针, 解引用得指向at字符串的指针,打印得at 笔试题8:
int main() { char *c[] = {"ENTER","NEW","POINT","FIRST"}; char**cp[] = {c+3,c+2,c+1,c}; char***cpp = cp; printf("%sn", **++cpp); printf("%sn", *--*++cpp+3); printf("%sn", *cpp[-2]+3); printf("%sn", cpp[-1][-1]+1); return 0; } 数组c是指针数组,数组元素分别是四个指向字符串首字符的字符指针 数组cp是指针数组,数组元素是4个二级指针。c是c数组首元素地址。 cpp是数组cp首元素地址。 ++cpp,cpp被改变,指向了cp数组的第二个元素,解引用得c+2是数组c第三个元素的地址 ,再解引用得数组c第三个元素,故打印POINT ++cpp,cpp被改变,指向了cp数组的第三个元素,解引用得c+1,--后将c+1变为c 再解引用得数组c的第一个元素,是指向ENTER字符串首字符E的字符指针, 加3指向了第二E,故打印ER cpp[-2]理解为*(cpp-2),cpp原指向cp第三个元素,减二解引用得c+3, 再解引用得数组c第四个元素,是指向FIRST字符串首字符的字符指针, 加3指向了S,故打印ST 理解为*(*(cpp-1)-1),cpp先-1解引用得c+2,再减一得c+1, 解引用得数组c第二个元素,是指向NEW字符串首字符N的字符指针, 加一指向了E,故打印EW



