1.字符指针
- 指针就是个变量,用来存放地址,地址是唯一的标识空间,内存单元有编号,编号=地址=指针
- 指针固定4/8字节(32位/64位)
- 指针有类型,类型觉得指针+1-1时的步长,决定了指针解引用时访问的大小
2.指针数组
指针数组是数组
字符数组–存放字符的数组
整型数组–存放整型的数组
指针数组–存放指针的数组,数组中的元素都是指针元素的
int* arr[5] char* ch[6]
3.数组指针
指针数组是数组,是存放指针的数组
数组指针是指针
字符指针–指向字符的指针
整型指针–指向整型的指针
浮点型指针-指向浮点型的指针
数组指针–指向数组的指针
数组名理解:数组名是数组首元素的地址,但是存在两个例外
- sizeof(数组名),这里表示整个数组,所以计算的是整个数组所占大小,单位为字节
- &数组名,这里数组名表示整个数组,取出的是数组的地址
int main(){
int arr[10]={0};
int (*p)[10]=&arr;
char *arr2[5];
char* (*pc)[5]=&arr2;
char arr3[5];
char(*p3)[5]=&arr3;
return 0;
}
数组指针到底有什么用?
sizeof数组指针可以获得数组的全部长度,所以数组指针存放的是数组的地址,而不是数组首元素的地址。
一维数组传参
//数组作为形参时,长度可以不写
void prints(int arr[],int sz){
for (int i = 0; i < sz; i++)
{
printf("%d ",arr[i]);
}
}
//一维数组传参传递首元素的地址,本质是指针,可以用int指针接收
void prints1(int* arr,int sz){
for (int i = 0; i < sz; i++)
{
printf("%d ",arr[i]);
}
}
int main(){
int arr[]={1,2,3,4,5};
int sz=sizeof(arr)/sizeof(arr[0]);
prints1(arr,sz);
return 0;
}
二维数组传参
void print(int arr[][5],int r,int c){
for (int i = 0; i < r; i++)
{
for (int j = 0; j < c; j++)
{
printf("%d ",arr[i][j]);
}
printf("\n");
}
}
//传递第一行数组的地址,可以用数组指针接收
void print(int (*arr)[5],int r,int c){
for (int i = 0; i < r; i++)
{
for (int j = 0; j < c; j++)
{
printf("%d ",*(*(arr+i)+j));
}
printf("\n");
}
}
//二维数组传参 传递二维数组的数组名其实是传递二维数组的首元素地址,也就是第一行数组整体的地址,注意不是第一行数组第一个元素的地址
int main(){
int arr[][5]={{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
print(arr,3,5);
return 0;
}
练习
int arr[5];//arr是能存储五个元素的数组
int *parr1[10];//parr1是一个指针数组,元素是整形指针类型,能存储十个元素
int (*parr2)[10];//parr2是数组指针,指向元素个数为10的整型数组的地址。
int (*parr3[10])[5];//parr3是数组,数组的元素是数组指针,每个元素指向长度为5的整型数组。parr可以存储十个数组指针
4.数组参数、指针参数
写代码的时候难免要把数组或指针传给函数,那函数参数该如何设计呢
一维数组传参
#include <stdio.h>
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);
}
二维数组传参
void test(int arr[3][5])//ok?二维数组作为参数,使用二维数组进行接收
{}
void test(int arr[][])//ok?//二维数组作为参数,必须指定列长度
{}
void test(int arr[][5])//ok?二维数组作为参数,行可以省略
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//ok?传递arr数组名,实际传递的是arr的首元素地址也就是第一行的数组地址,不能使用整型指针接收
{}
void test(int* arr[5])//ok?传递arr数组名,实际传递的是arr的首元素地址也就是第一行的数组地址,可以使用指针数组接收
{}
void test(int (*arr)[5])//ok?传递arr数组名,实际传递的是arr的首元素地址也就是第一行的数组地址,可以使用数组指针接收
{}
void test(int **arr)//ok?二级指针是用来接收一级指针的地址,所以不行
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}
一级指针传参
void print(int *p, int sz)//一级指针传参,使用一级指针接收就可以
{
int i = 0;
for(i=0; i<sz; i++)
{
printf("%d\n", *(p+i));
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9};
int *p = arr;
int sz = sizeof(arr)/sizeof(arr[0]);
//一级指针p,传给函数
print(p, sz);
return 0;
}
当一个函数以一级指针为参数,函数能接收什么参数
void test(int *p);
int a=10;
int *p=&a;
int arr[10];
test(&a);//整形变量的地址
test(p);//传递一级指针
test(arr);//传递一维数组的数组名
二级指针传参
void test(int** ptr)
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int*p = &n;
int **pp = &p;
test(pp);//传递二级指针
test(&p);//传递一级指针的地址
return 0;
}
当一个函数以二级指针为参数,函数能接收什么函数
void test(int **pp);
int a = 10;
int *p = &a;
int **pp=&p;
test(pp);//可以接收二级指针
test(&p)//可以接收一级指针的地址
int* arr[10];
test(arr)//可以接收指针数组的首元素地址。因为指针数组的元素为指针类型,指针类型的地址就是二级指针。
5.函数指针
数组指针—指向数组的指针—存放的是数组的地址—&数组名得到
函数指针—指向函数的指针—存放的是函数的地址—函数名也是函数的地址
int add(int x,int y){
return x+y;
}
int main(){
int (*pf1)(int,int) = add;
int (*pf2)(int,int) = &add;
// printf("%d",pf1==pf2);
printf("%d",(pf1)(3,2));
return 0;
}
6.函数指针数组
int (* p[10])(int),本质上是一个数组
指针数组—char * arr[]存放的是字符指针
整型指针数组—int * arr2[]存放的是整型指针
函数指针数组是数组,数组的元素是函数指针,也就是函数的地址
用函数指针数组完成计算机
#include<stdio.h>
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 = 1,ret = 0,A1 = 0,A2 = 0;
int (*cal[5])(int,int)={NULL,Add,Sub,Mul,Div};
do
{
printf("********************\n");
printf("***1.Add****2.Sub***\n");
printf("***3.Mul****4.Div***\n");
printf("***0.Exit***********\n");
printf("********************\n");
scanf("%d",&input);
if (input==0)
{
printf("退出系统");
}else if (input>0&&input<5)
{ printf("请输入两个数字\n");
scanf("%d %d",&A1,&A2);
ret = cal[input](A1,A2);
printf("ret = %d\n",ret);
/* code */
}else{
printf("输入错误\n");
}
} while (input);
return 0;
}
7.指向函数指针数组的指针
int(* (*p)[10])(int)
int main(){
int a = 10;
int b = 20;
int c = 30;
int *arr[] = {&a,&b,&c};//整型指针数组
int (*p)[3] = &arr;//指向整型数组的指针
//函数指针数组 - 数组 - 存放的是函数的地址
int (*pfArr[5])(int,int)={NULL,Add,Sub,Mul,Div};
//指向函数指针数组的指针
int(*(*pF)[3])(int,int) = &pfArr;
}
8.回调函数
依赖函数指针,有函数指针才能实现回调函数
回调函数是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向函数时,我们说这就是回调函数。回调函数不应该由函数的实现方直接调用,而是在特定的事件或条件发生时由另一方调用,对于该事件或条件进行相应。
简单来说就是不可以直接使用函数名+()的形式调用,需要将回调函数的地址传给函数指针,通过函数指针来调用。
通过函数指针调用回调函数
void cals(int (*p)(int,int)){
int a1,a2;
printf("请输入两个数字\n");
scanf("%d %d",&a1,&a2);
int ret = p(a1,a2);
printf("ret = %d\n",ret);
}
int main(){
int input = 1,ret = 0,A1 = 0,A2 = 0;
// int (*cal[5])(int,int)={NULL,Add,Sub,Mul,Div};
do
{
printf("********************\n");
printf("***1.Add****2.Sub***\n");
printf("***3.Mul****4.Div***\n");
printf("***0.Exit***********\n");
printf("********************\n");
scanf("%d",&input);
switch (input)
{
case 1:
cals(Add);
break;
case 2:
cals(Sub);
break;
case 3:
cals(Mul);
break;
case 4:
cals(Div);
break;
case 0:
printf("退出系统\n");
break;
default:
printf("输入错误,重新输入\n");
break;
}
} while (input);
return 0;
}
qsort的使用
qsort是头文件stdlib.h中的函数。此函数有四个参数
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
第一个base代表要排序数组的第一个元素的地址一般传入数组名,如果想部分排序,需要获得其实排序元素的地址。
第二个代表要排序的元素的个数
第三个代表要排序数组中的每个元素的大小,以字节为单位
第四个需要传入一个compare函数,compare函数参数必须为void类型的指针。因为qsort()为了对所有类型排序。void类型指针可以接收任何类型的指针。compare的返回值为int。
类型为void的指针补充:类型为void类型的指针不具备访问内存空间的能力。但是此类型指针的特点是可以存储任何类型的指针,后续使用时可以把void类型转换为当时转换前的指针类型。
// int main(){
// int a = 10;
// int b = 20;
// int c = 30;
// int *arr[] = {&a,&b,&c};//整型指针数组
// int (*p)[3] = &arr;//指向整型数组的指针
// //函数指针数组 - 数组 - 存放的是函数的地址
// int (*pfArr[5])(int,int)={NULL,Add,Sub,Mul,Div};
// //指向函数指针数组的指针
// int(*(*pF)[3])(int,int) = &pfArr;
// }
struct stu
{
char name[20];
int age;
};
// void print_arr(int* arr,int len){
// for (int i = 0; i < len; i++)
// {
// printf("%d ",arr[i]);
// }
// printf("\n");
// }
// int compare(const void* p1,const void* p2){
// return *(int*)p1-*(int*)p2;
// }
// int main(){
// int arr[]={9,8,7,6,5,4,3,2,1};
// int sz=sizeof(arr)/sizeof(arr[0]);
// print_arr(arr,sz);
// qsort(arr,sz,sizeof(arr[0]),compare);
// print_arr(arr,sz);
// return 0;
// }
// void print_struct(struct stu *stu,int sz){
// for (int i = 0; i < sz; i++)
// {
// printf("name = %s , age = %d\n",(stu+i)->name,(stu+i)->age);
// }
// }
// int compare(void *p1,void *p2){
// return ((struct stu*)p1)->age-((struct stu*)p2)->age;
// }
// int compare_str(void *p1,void *p2){
// return strcmp(((struct stu*)p1)->name,((struct stu*)p2)->name);
// }
// int main(){
// struct stu Stus[3]={{"zvjing",61},{"xanjiam",20},{"yhangfei",43}};
// print_struct(Stus,3);
// // qsort(Stus,3,sizeof(Stus[0]),compare);
// qsort(Stus,3,sizeof(Stus[0]),compare_str);
// print_struct(Stus,3);
// return 0;
// }
9.指针和数组笔试题解析
int main(){
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a)); //a为数组名 sizeof数组名为数组的整体长度,单位为字节 ans=16字节
printf("%d\n",sizeof(a+0));//a+0为数组的首元素地址,sizeof地址为指针的大小,单位是字节 ans=4/8
printf("%d\n",sizeof(*a));//*a为数组的第一个元素,sizeof int ans=4
printf("%d\n",sizeof(a+1));//a+1为数组第二个元素的地址,sizeof地址 ans=4/8
printf("%d\n",sizeof(a[1]));//a[1]为数组的第二个元素,等于sizeof int ans=4
printf("%d\n",sizeof(&a));// &a为数组的地址,sizeof地址 ans=4/8
printf("%d\n",sizeof(*&a));//&a为数组整体的地址,类型为int *p[4],*p为数组的地址,等价于sizeof(a),ans=16
printf("%d\n",sizeof(&a+1));//&a为数组的地址,&a+1会跨国整个数组,ans=4/8
printf("%d\n",sizeof(&a[0]));//&a[0]为数组首元素的地址,ans=4/8
printf("%d\n",sizeof(&a[0]+1));//为第二个元素的地址 ans=4/8
return 0 ;
}
int main(){
char arr[] = {'a','b','c','d','e','f'};//字符数组依次赋值,不会自动给末尾加结束符。
printf("%d\n", sizeof(arr));//arr为数组名。sizeof数组名代表数组的整体长度,单位为字节。ans = 6
printf("%d\n", sizeof(arr+0));//数组首元素的地址 ans=4/8
printf("%d\n", sizeof(*arr));//sizeof 第一个元素为a, sizeof('a') ans = 1
printf("%d\n", sizeof(arr[1]));//sizeof 第二个元素 ans = 1
printf("%d\n", sizeof(&arr));//sizeof 地址,ans = 4/8
printf("%d\n", sizeof(&arr+1));//跨越整个数组 ans = 4/8
printf("%d\n", sizeof(&arr[0]+1));//sizeof 第二个元素的地址 ans=4/8
return 0;
}
int main(){
char arr[] = {'a','b','c','d','e','f'};//字符数组依次赋值,不会自动给末尾加结束符。
printf("%d\n", strlen(arr));//由于不会自动加结束符,所以所以答案为随机值 ans = x x>=6
printf("%d\n", strlen(arr+0));//为数组首元素的地址,ans = x x>=6
//printf("%d\n", strlen(*arr));//strlen('a') err
//printf("%d\n", strlen(arr[1]));//strlen('b') err
printf("%d\n", strlen(&arr));//虽然&arr的类型为 char *p[6],但是&arr的值和arr的值一样,strlen的参数为const char *p,只会warning。不会影响结果 ans=x x>=6 部分编译器报错
printf("%d\n", strlen(&arr+1));//&arr+1跨越了整个数组 ans=x-6 部分编译器报错
printf("%d\n", strlen(&arr[0]+1));//从第二个元素开始 ans = x-1
return 0;
}
int main(){
char arr[] = "abcdef";//通过这种形式给字符串赋值,会自动在结尾加结束符
printf("%d\n", sizeof(arr));//sizeof数组名 获取数组的长度 单位为字节 ans = 7 因为又结束符
printf("%d\n", sizeof(arr+0));//为数组首元素的地址 ans=4/8
printf("%d\n", sizeof(*arr));//sizeof('a') ans = 1
printf("%d\n", sizeof(arr[1]));//sizeof('b') ans = 1
printf("%d\n", sizeof(&arr));//&arr为数组的地址 ans=4/8
printf("%d\n", sizeof(&arr+1));//&arr+1跨域整个数组 ans=4/8
printf("%d\n", sizeof(&arr[0]+1));//为第二个元素地址 ans = 4/8
return 0;
}
int main(){
char arr[] = "abcdef";//通过这种形式给字符串赋值,会自动在结尾加结束符
printf("%d\n", strlen(arr));//判断结束符前的字符 ans = 6
printf("%d\n", strlen(arr+0));//从第一个元素到最后一个元素 ans = 6
// printf("%d\n", strlen(*arr));//strlen的参数为char * *arr='a' err
// printf("%d\n", strlen(arr[1]));//arr[1]='b',b
printf("%d\n", strlen(&arr));//ans = 6
printf("%d\n", strlen(&arr+1));//随机值
printf("%d\n", strlen(&arr[0]+1));//从第二个元素到最后 ans = 5
return 0;
}
int main(){
char *p = "abcdef";//实际上p存储的是字符a的地址
printf("%d\n", sizeof(p));//p存储的是a的地址 ans=4/8
printf("%d\n", sizeof(p+1));//存储的是b的地址 ans=4/8
printf("%d\n", sizeof(*p));//*p为字符a,ans = 1
printf("%d\n", sizeof(p[0]));//p[0]==*p ans = 1
printf("%d\n", sizeof(&p));//&p为指针的地址 ans=4/8
printf("%d\n", sizeof(&p+1));//跨越一个指针的距离 ans=4/8
printf("%d\n", sizeof(&p[0]+1));//指向b的地址 ans=4/8
return 0;
}
int main(){
char *p = "abcdef";//实际上p存储的是字符a的地址,自动补充结束符
printf("%d\n", strlen(p));//a开始到f结束 ans=6
printf("%d\n", strlen(p+1));//b开始 ans=5
// printf("%d\n", strlen(*p));//strlen参数为char 不是字符'a' *p err
// printf("%d\n", strlen(p[0]));//strlen参数为char 不是字符'a' *p err
// printf("%d\n", strlen(&p));//,p为指针,取指针的地址没有意义,随机值
// printf("%d\n", strlen(&p+1));//p地址+1 随机值
printf("%d\n", strlen(&p[0]+1));//b-f anns=5
return 0;
}
int main(){
int a[3][4] = {0};
printf("%d\n",sizeof(a));//sizeof数组名获得的是数组整体的大小,数组一共十二个元素所以ans = 48
printf("%d\n",sizeof(a[0][0]));//a[0][0]为数组第0行的第0个元素,类型为int ans = 4
printf("%d\n",sizeof(a[0]));//a[0]为数组第0行的数组名,sizeof数组名代表数组整体的大小,也就是第0行的大小 ans = 16
printf("%d\n",sizeof(a[0]+1));//a[0]为数组第0行的数组名,这里是第0行的首元素地址,加一之后为第0行第一个元素的地址 ans = 4/8
printf("%d\n",sizeof(*(a[0]+1)));//为第0行第一列元素的值,ans = 4
printf("%d\n",sizeof(a+1));//这里a代表首元素的地址,也就是第0行的地址,加一变成第一行的地址,ans = 4/8
printf("%d\n",sizeof(*(a+1)));//a+1代表第1行的地址,解引用代表第1行的数组名。 ans = 16
printf("%d\n",sizeof(&a[0]+1));//a[0]代表数组第0行的数组名,对数组名&获得的是整个数组的地址,在加一变成第1行数组的地址 ans = 4/8
printf("%d\n",sizeof(*(&a[0]+1)));//代表第一行的数组名 ans = 16
printf("%d\n",sizeof(*a));//代表第一行的数组名 ans = 16
printf("%d\n",sizeof(a[3]));//sizeof不会真实访问a[3] 等价于sizeof(a[1]) ans = 16
return 0;
}
总结:数组名的意义
- 数组名单独放在sizeof中时,计算的时数组的长度
- &数组名,取出的是数组的地址,类型为int(*)[]
- 除此之外,数组名都表示数组首元素的地址
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获得的是数组的地址,&a+1会跨越整个数组,也就是指向数组最后一个元素的下一个位置。然后将&a-1强转成int*。ptr-1就指向了5的地址。a代表数组首元素的地址,a+1就代表第二个元素的地址。所以输出 2,5。
2
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
p为结构体类型的指针,p+1应该跳过一个结构体类型的大小也就是20字节。所以p+1=00100014
把p转换成int,那么p+1就是+1。p=00100001
把p转换为int*,p+1跳过整型的大小。p=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就指向了4后面那个元素的地址,随后被强转成了int*。ptr[-1]等于*(ptr-1)就等于4。小端存储就低位存在低地址,高位存在高地址。所以*ptr2从第二个字节开始向后四个字节。输出200000
4
#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;
}
数组里的元素为逗号表达式。实际上存储的值为 1,3,5。p=a[0],p存储的是数组首元素的地址也就是第一行的地址。p[0]等于第一行的第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和a都是地址,地址-地址代表中间相差了几个元素。之间差了四个元素所以%d的差值为-4,16进制的-4为FFFFFFFC,
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可以获取数组的地址。&aa+1就指向了10后面的那个元素的地址。*(ptr-1)就等价于指向了10。aa代表数组首元素的地址。aa+1代表指向了第二行。*(aa+1)就获得了第二行的数组名。第二行第一个元素的地址-1就等于上一行最后一个元素的地址所以输出5

7
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
pa是存放char*类型的指针。pa++应该跳过一个char*指针类型,指向存放at的指针,所以输出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,cpp存储的是char**类型的变量,++cpp将cpp指针向后移动一位指向c+2的地址*cpp指向c+2,**cpp代表c+2的地址。也就是P的地址,所以输出POINT
此时cpp指向c+2的地址,*++cpp=c+1.*–c+1+3=*c+3=E的地址,所以输出ER
cpp[-2]=cpp指针向前移动两个元素,等于c+3,*(c+3)=P的地址,在加三等于S的地址,输出ST
cpp[-1]表示cpp指向向前移动一个元素,等于c+2,*(c+2-1)=*(c+1)=N的地址,加一等于E的地址,输出ew