一、字符指针
1、字符指针的一般使用:
int main()
{
char ch = 'w';
char* pc = &ch;
*pc = 'w';
return 0;
}
2、字符指针的另一种使用方式
int main()
{
char* ps = "hello world";
printf("%s\n",ps);
return 0;
}
代码分析:
①ps中存放的是字符串“hello world”第一个字符的地址。
②解引用打印的是h,打印字符串是打印的一整串字符。
③不能解引用改掉字符串“hello world”中的字符。因为“hello world”是字符常量。如果解引用改的话,会让程序挂掉。所以一般在这种使用方式之前加上const
④如果两个指针都存放的是同一个字符常量,那么其中存放的首字符地址也是相同的。
⑤与数组的区别:数组是开辟一块空间,将字符串存储在里面。而字符指针只是保存的字符常量sh首字符的地址。
二、指针数组
1、指针数组是存放指针变量的数组
2、指针数组的使用
int main()
{
int a[] = { 1, 2, 3, 4, 5 };
int b[] = { 2, 3, 4, 5, 6 };
int c[] = { 3, 4, 5, 6, 7 };
int* arr[3] = { a, b, c };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5;j++)
{
printf("%d ", *(arr[i] + j));
//printf("%d ",arr[i][j]);
}
printf("\n");
}
return 0;
}
代码分析:上述代码类似于二维数组,但是不是二维数组。
三、数组指针
1、数组指针是一种指针,其中存放的是一个数组的地址,数组名表示的是数组首元素的地址,而&数组名表示的是整个数组的地址。
2、数组指针的表示
#include<stdio.h>
int main()
{ int arr[10]={1,2,3,4,5};
int (*parr)[10]=&arr;//数组指针指向的是一个数组
return 0;
}
int (*parr)[10]; (*parr)表示一个存放地址的指针变量,(*parr)[10]表示的是指针变量指向一个包含十个元素的数组,int (*parr)[10]表示的是这个指针变量所指向的数组,数组中的元素类型为int类型。
☆☆[ ]中的数字不能省略
3、数组指针的使用(一般用在二维数组中)
①在一维数组中(在一维数组中使用有点南辕北辙)
int main()
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int(*parr)[10] = &arr;
for (int i = 0; i < 10; i++)
{
printf("%d ", *((*parr) + i));
}
return 0;
}
②在二维数组中
void print(int (*parr)[5], int r, int c)
{
for (int i = 0; i < r; i++)
{
for (int j = 0; j < c; j++)
{
printf("%d ", *(*(parr + 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;
}
二维数组的数组名表示的是首元素的地址,二维数组的首元素为{1,2,3,4,5},所以首元素的地址为一个数组的地址。
4、分析
int(*parr)[10];数组指针,该指针指向一个数组,数组中有十个元素,每个元素是int类型。
int (*parr[10])[5];parr是一个可以存放十个数组指针的数组,存放的每个数组指针可以指向一个数组,数组中有五个元素,每个元素是int类型。
四、数组传参和指针传参
1、一维数组传参
void test(int arr[])//数组传参方式一
{};
void test(int arr[10])//数组传参方式二
{};
void test(int *arr)//指针传参
{};
void test2(int* arr[20])
{};
void test2(int* *arr)
{};
int main()
{
int arr[10] = { 0 };
int *arr2[20] = { 0 };
test(arr);
test2(arr2);
return 0;
}
2、二维数组传参
void test(int arr[3][5])//数组传参方式一
{};
void test(int arr[][5])//数组传参方式二
{};
void test(int arr[][])//❌
{};
void test(int *arr)//❌
{};
void test(int *arr[5])//指针数组,❌
{};
void test(int(*arr)[5])//数组指针,指针传参方式
{};
void test(int **arr)//❌
{};
int main()
{
int arr[3][5] = { 0 };
test(arr);
return 0;
}
3、一级指针传参
void test1(int* p)//函数都用一级指针来接收
{};
void test2(int *p)
{};
void test3(int *pa)
{};
void test4(int* pa)
int main()
{
int arr[10];
int a = 100;
int* pa = &a;
int *parr = arr;
test1(arr);//可以直接传地址过去
test2(parr);//可以传指针变量过去
test3(&a);
test4(pa);
return 0;
}//当传递地址给函数时,函数需用指针来接收。
//当函数用指针来接收时,需要传递地址给它或者传递指针变量给它。
4、二级指针传参
void test1(int** pp)//函数都会用二级指针来接收
{};
void test2(int** pp)
{};
void test3(int** pp)
{};
int main()
{
int a = 10;
int* arr[10];
int* pa = &a;
int** ppa = &pa;
test1(ppa);//可以传递二级指针的变量名
test2(&pa);//可以传递一级指针的地址
test3(arr);//可以传递一级指针数组的数组名
return 0;
}//当一个一级指针地址传给函数时,函数需要用二级指针来接收。
//当函数用一个二级指针来接收时,传递给它的参数可以是二级指针的变量名、一级指针的地址和一级指针数组的数组名。
五、函数指针
1、存放函数地址的指针叫做函数指针。&函数名和函数名都表示的是函数的地址。
2、函数指针的表示
int Add(int x, int y)
{
return x + y;
}
int main()
{
int(*padd)(int, int) = Add;
return 0;
}
①int (*)(int,int)这是函数指针类型
②padd是变量-->*padd是一个指针变量-->int (*padd)(int,int)一个指针,其中存放的是一个参数为两个int,返回类型为int的函数的地址。
3、函数指针的使用
int Add(int x, int y)
{
return x + y;
}
int main()
{
int(*padd)(int, int) = Add;//可以得到padd==Add
int ret = (*padd)(3, 5);//一般的函数调用是Add(3,5)-->padd(3,5) 所以这三种方法是完全等价的
printf("%d\n",ret);
return 0;
}
①函数指针的使用可以写成三种方式:(*padd)(3,5)、padd(3,5)、Add(3,5)
4、分析
void (*signal(int,void(*)(int)))(int);
代码分析:signal(int,void(*)(int))说明signal是一个函数名,而()内的为函数参数,一个参数为int类型,另一个参数为函数指针类型(此函数指针类型为参数为int类型,返回值为void类型的),而返回类型也为函数指针类型。
易于理解的书写方式(但是不和语法的):void(*)(int) signal(int,void(*)(int))
但是可以用typedef进行类型重定义,重定义函数指针类型的书写方式为:
typedef void (*pfun_t)(int); //正确的书写方式
typedef void(*)(int) pfun_t; //错误的书写方式
六、函数指针数组
1、存放函数指针的数组,本质是一个数组。
2、函数指针数组的表示方式
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int main()
{
int(*pf1)(int, int) = Add;//分开存放的方式
int(*pf2)(int, int) = Sub;
int(*pf[2])(int, int) = {Add,Sub};//用函数指针数组进行存放
return 0;
}
代码分析:pf先和[ ]结合-->pf[2]说明是一个数组,int(*)(int,int)是一个函数指针类型-->所以
int(*pf[2])(int,int)是一个函数指针类型的数组。
3、函数指针数组的使用
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 a = 0;
int b = 0;
int input = 0;
scanf("%d",&input);
scanf("%d%d",&a,&b);
int(*ppp[5])(int, int) = {NULL,Add,Sub,Mul,Div};
int ret = (ppp[input])(a, b);
printf("%d\n",ret);
return 0;
}
这个数组被称为转移表。转移表是函数指针数组的应用。
七、指向函数指针数组的指针
1、指针取出的是函数指针数组的地址。
2、指向函数指针数组的指针的表示方式
int(*p)(int,int)函数指针-->int(*p[2])(int,int)函数指针的数组-->int(*(*p)[2])(int,int)指向函数指针数组的指针。
int (*pfArr[5])(int,int)={NULL,Add,Sub,Mul,Div};
int (*(*p)[5])(int,int)
八、回调函数
1、回调函数就是一个通过函数指针调用的函数,如果你把函数的指针作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
2、回调函数的理解
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 Calc(int(*pf)(int, int))
{
int a = 0;
int b = 0;
int input = 0;
scanf("%d", &input);
scanf("%d%d", &a, &b);
return pf(a, b);
}
int main()
{
int input = 0;
scanf("%d", &input);
int res = Calc(Add);
printf("%d\n",res);
return 0;
}
利用回调函数实现的库函数,如qsort(快排序),现用冒泡排序模仿qsort的实现:
void qsort(void*base,size_t num,size_t size,int(*compar)(const void*,const void*))
第一个参数为待排序数据中第一个元素的地址。
第二个参数为待排序数据的个数。
第三个参数为待排序数据一个元素的大小。
第四个参数为比较两个数据的函数。
第四个参数两个参数,第一个参数-第二个参数,返回值>0说明前一个数比后一个数大,那么进行交换,返回值为0说明两个数一样大,返回值<0说明第一个数小于第二个数,不进行交换。所以就将数据升序排列了。想要降序排列就让第四个参数的第二个参数-第一个参数,就完成了降序排列。
九、指针和数组面试题的解析
表达式有两个属性,一个是值属性,一个是类型属性。值属性是得到它的值,而类型属性是得到它的类型。
sizeof(),()中的内容是不计算的。
#define INT_A int*
typedef int* int_a;
INT_A a, b;
int_a c, d;
上述四个变量b是整型,其余三个都是int*类型的。
因为#define只是简单的替换,将int*替换成了INT_A因此,相当于int* a,b;
这个代码就相当于是int *a;int b;
要想定义两个int*类型就应该写成int *a,*b;
而typedef 是将int* ,int_a等价,所以int_a c,d;
就相当于int *a,*b;
可以与此类比的如float类型,float e,f;那么e、f都是float类型,int_a与此相同。
int main()
{
short s = 5;
int a = 4;
printf("%d\n",sizeof(s=a+6));//2 因为两个整型计算完之后放到了short类型里面
printf("%d\n",s);// 5 因为sizeof()里面的表达式不参与运算。
return 0;
}
题目一:
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf( "%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
//程序的结果是什么?
题目二:
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
int main()
{
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
题目三:
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;
}
题目四:
#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;
}
题目五:
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;
}
题目六:
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;
}
题目七:
#include<stdio.h>
int main()
{
char* a[] = {"work","at","alibaba"};
char** pa = a;
pa++;
printf("%s\n",*pa);
return 0;
}