4.通过指针引用数组,重要面试
文章目录
4.1定义一个指针变量指向数组
01指向数组首元素的地址
02等于数组名:指向数组起始位置
#include <stdio.h>
int main()
{
01
/*回顾我们之前的写法
* int a =10;
* int *p;
* p =&a; */
02
//现在我们变成了数组
int arr[3] = {1,2,3};
int *p;
01.p = &arr[0];//指针指向数组的首地址
//还有另外一种写法
02.p =arr; //等于整个数组名也是取的数组的首地址,,,注意前面不要 + &
printf("address of a is %p\n ",&a);//指向一个固定的地址
//数组名就是数组的首地址,数组的首地址就是首个元素的地址
printf("address of p is %p\n",p);
return 0;
}
4.2指针增量和数组的关系
我们知道数组在一个内存中是一个连续的地址,那么指针的增量与数组之间又存在着什么关系呢?
#include <stdio.h>
int main()
{
int arr[3] = {1,2,3};
int *p;
p =arr;
printf("第0元素的数值是 %d\n",*p);
printf("第1元素的数值是 %d\n",*(p+1));//这里我们的+1并不是值+1,而是指针偏移,类型为整型,偏移四个字节,字符型,偏移一个字节
printf("第2元素的数值是 %d\n",*(p+2));//注意必须要加括号,下面的实操只是凑巧加起来是那个数,*p会首先结合然后再相加
return 0;
}
但是通过上面的方法有没有感觉很蠢,如果他有一百个元素呢?难道需要输入一百次嘛?所以我们出现了现在的for循环
#include <stdio.h>
int main()
{
int arr[3] = {1,2,3};
int *p;
p =arr;
for(int i =0;i<3;i++){
printf("第%d元素的数值是 %d\n",i,*(p+i));
printf("地址是 %p\n",(p+i));
}
return 0;
}
4.3通过指针引用数组
#include <stdio.h>
int main()
{
int arr[3] = {1,2,3};
int *p;
p =arr;
//方法一
for(int i =0;i<3;i++){
printf("%d\n",*p++); //这个的意思是指针先*取值,然后在此基础上面再+1
}
p =arr; //这里我们需要注意的是重新给指针变量p 赋值,要不然经过上面的一系列操作,指针已经跑飞了
//方法二
for(int i =0;i<3;i++){
printf("%d\n",*p);
p++; //指针进行 偏移
}
return 0;
}
1.下标法
就是之前学过的数组的下标法遍历arr[ i ];
2.指针法
2.1偏移
上面的例子都是说的偏移,在此不再赘述
2.2取内容
- 指针当作数组名,下标法访问
- 数组名拿来加
///这个与上面的指针偏移不一样,所以不会跑飞,注意区别他们的不同的地方
#include <stdio.h>
int main()
{
int arr[3]={1,2,3};
int *p =arr;
//方法一:
for(int i =0;i<3;i++)
{
printf("第%d个元素是%d\n",i,p[i]);//见怪不怪哈哈哈,他确实可以这样写
}
//方法二:
for(int i =0;i<3;i++)
{
printf("第%d个元素是%d\n",i,*(p+i));//见怪不怪哈哈哈,他确实可以这样写
printf("第%d个元素是%d\n",i,*(arr+i));//见怪不怪哈哈哈,他确实可以这样写
}
return 0;
}
数组名和指针的区别
1.arr++可行否??
///这个与上面的指针偏移不一样,所以不会跑飞,注意区别他们的不同的地方
#include <stdio.h>
int main()
{
int arr[3]={1,2,3};//那这个数组是一个常量
int *p =arr;//首先p是一个保存地址的变量,那他保存的地址是可以改的,这叫做变量
//方法一:
for(int i =0;i<3;i++)
{
printf("第%d个元素是%d\n",i,*p++);//,他这样可以写
}
//方法二:
for(int i =0;i<3;i++)
{
printf("第%d个元素是%d\n",i,*arr++);//那这样呢?数组常量可以吗?
} //编译不过,指针常量
return 0;
}
我们看到arr++是用不了的,
2.sizeof的使用
#include <stdio.h>
int main()
{
int arr[3]={1,2,3};
int *p =arr;
printf("sizeof arr is %d\n",sizeof(arr));//一个元素四个字节,一共三个,所以12个字节
printf("sizeof arr is %d\n",sizeof(p));//在OS中,用8个字节来表示一个地址
printf("sizeof int is %d\n",sizeof(int));
printf("sizeof pointer is %d\n",sizeof(int *));//在OS中,用8个字节来表示一个地址
printf("sizeof pointer is %d\n",sizeof(char *));//在OS中,用8个字节来表示一个地址
return 0;
}
两种方法效率对比
编程案例
1.函数封装数组初始化,遍历
练习函数数组指针结合
//首先来回顾一下我们以前写的代码
#include <stdio.h>
void initarr(int arr[],int size)
//第二种传递形式参数的方法
void printfarr(int *parr,int size)
//练习函数指针数组结合
{
for(int i=0;i<size;i++)
{
printf("请输入第%d个元素\n",i+1);
scanf("%d",&arr[i])
/*第二种方法
scanf("%d",parr);//因为这个本身就是地址了
指针偏移 parr++; */
}
}
void printfarr(int arr[],int size)
{
for(int i=0;i<size;i++)
{
printf("%d\n",arr[i]);
}
}
int main()
{
int arr[5];
int size = sizeof(arr)/sizeof(arr[0]);
initarr(arr,size);//实际参数,数组的首地址:名,元素的地址
printfarr(&arr[0],size);//那么我们知道,指针变量是存放地址的变量
return 0;
}
#include <stdio.h>
void initarr(int *parr,int size)
//练习函数指针数组结合
{
for(int i=0;i<size;i++)
{
printf("请输入第%d个元素\n",i+1);
scanf("%d",parr++);//因为这个本身就是地址了
}
}
void printfarr(int *parr,int size)
{
for(int i=0;i<size;i++)
{
printf("%d\n",*parr++);
}
}
int main()
{
int arr[5];
int size = sizeof(arr)/sizeof(arr[0]);
initarr(arr,size);//实际参数,数组的首地址:名,元素的地址
printfarr(&arr[0],size);//那么我们知道,指针变量是存放地址的变量
return 0;
}
2.将数组中的n个元素按逆序存放,函数封装
如何逆序存放呢? 第0个跟第4个换,第1个跟第3个换,2不动即可,,直接做一个临时变量,茶杯法直接交换即可
如果是奇数的话
那如果是偶数呢?结果同样成立
#include <stdio.h>
void initarr(int *parr,int size)
//练习函数指针数组结合
{
for(int i=0;i<size;i++)
{
printf("请输入第%d个元素\n",i+1);
scanf("%d",parr++);//因为这个本身就是地址了
}
}
void reversedarr(int *parr,int size)
//练习函数指针数组结合
{
for(int i=0;i<size/2;i++)
{
int j=size-1-i;
int tmp;
tmp = parr[i];
parr[i] = parr[j];
parr[j] = tmp;
//以下是对上面的函数用指针来实现,自我感觉变抽象了哈哈哈哈
tmp = *(parr+i);
*(parr+i) = *(parr+j);
*(parr+j) = tmp;
}
putchar('\n');
}
void printfarr(int *parr,int size)
{
for(int i=0;i<size;i++)
{
printf("%d ",*parr++);//因为这个本身就是地址了
}
}
int main()
{
int arr[5];
int size = sizeof(arr)/sizeof(arr[0]);
initarr(arr,size);//实际参数,数组的首地址:名,元素的地址
printfarr(&arr[0],size);//那么我们知道,指针变量是存放地址的变量
reversedarr(arr,size);
printfarr(&arr[0],size);
return 0;
}
5.指针与二维数组
父子数组,为了研究清楚地址的概念,把二维回归到一维数组
二维数组本质还是数组,不同点是数组元素还是个数组(子数组),以往的我们 int arr[ ]={1,2,3};如果arr+1 = 1;那么这次我们,如果是a[0]+1呢?如下图,a[0]是第一行第一列的地址,a[0]+1=3;
首地址的表示:1.数组名 2.首个元素的地址。
**下面我们来思考几个问题?? 1.a是谁的地址 2.a[0]又是谁的地址 3.a跟 (a+0)呢?
- a是父数组,地址是第一整行的地址,a+1向下偏移一行,4*4=16个字节
- a[0]是子数组,地址为第一行第一列的地址,
- 以往的 int *p = arr; int arr[ ]={1,2,3}, 我们知道指针变量是存放地址的变量,那么arr就是数组的首地址, *arr就是取内容之后,即 *arr=1;所以 *a就是第一行第一列的地址,与a[0]等价,也与 *(a+0)等价
那么a[0]+1又是什么意思呢?
- a[0]+1第0行第一列的地址,是地址的意思,,*(a+0)+1
- 也可以说是第0个子数组的第1个元素的地址
- 而第0个子数组的第1个元素表示方式是a[0] [1],不要乱
//一定要记住arr[0]=*arr=*(arr+0)
#include <stdio.h>
int main()
{
int arr[3][4] ={{1,2,3,4};{5,6,7,8};{9,10,11,12}};
printf("arr是父亲数组:%p,偏移1后是%p\n",arr,arr+1);
printf("arr[0]是子数组:%p,偏移1后是%p\n",arr[0],arr[0]+1);
printf("arr[0]是子数组:%p,偏移1后是%p\n",*arr,*(arr+0)+1);
return 0;
}
可以看到父数组偏移了16个字节,子数组是4个字节
树立认知
//一定要记住arr[0]=*arr=*(arr+0)
#include <stdio.h>
int main()
{
int arr[3][4] ={{1,2,3,4};{5,6,7,8};{9,10,11,12}};
for(int i= 0;i<3;i++){
for(int j =0;j<4;j++)
{//这是我们以前的写法,工作后也可以这样写,但面试就要用指针了
printf("address:0x%p,data:%p\n",&arr[i][j],arr[i][j]); //arr[0]+0 = &arr[0][0]
printf("address:0x%p,data:%p\n",arr[i]+j,*(arr[i]+j)); //笔试题考,这个就是指针的偏移来取到地址
printf("address:0x%p,data:%p\n",*(arr+i)+j,*(*(arr+i)+j)); //把arr[0]=*(arr+0)替代,是不是傻眼了
}
}
return 0;
}
小总结(嵌入式工程师笔试题会考)
6.数组指针
数组指针,一个指向数组的指针
指针数组,一堆指针组成的一个数组
#include <stdio.h>
int main()
{
int arr[3][4] ={{11,22,33,44},{55,66,77,88},{44,55,66,77}};
//那么之前我们说的arr++,是否可以呢?arr++增加的数值是一整个数组(arr[0][0],arr[1][0]),
//而p++是单个偏移(arr[0][0],arr[0][1])
int i,j;
int *p;
//01.
p = arr;
//02.
p = &arr[0][0];
for(i=0;i<3;i++)
{
for(j=0;j<4;j++)
{
printf("%p\n",arr++);
printf("%p\n",p++);
}
}
return 0;
}
例题
输出二维数组任意行列的数
#include <stdio.h>
void tips_input(int *hang,int *lie)
{
printf("输入你想要的行列\n");
scanf("%d%d",&hang,&lie);
puts("done! ");
}
int get_data(int (*p)[4],int hang,int lie)
{
int data;
data = *(*(p+hang)+lie);
return data;
//第二种简单的写法 return arr[hang][lie];
}
int main()
{
int arr[3][4] = {{11,22,33,44},{55,66,77,88},{99,01,02,03}};
int hang,lie;
int data;
//1.提醒用户输入想要的行列值
tips_input(&hang,&lie);
//2.找到行列值所对应的数
data = get_data(arr,hang,lie);
//3.打印出来
printf("第%d行,第%d列的数为%d",hang,lie,data);
return 0;
}
7.函数指针
指向一个函数地址的指针,类似于一个函数的地址入口,定义“函数地址”,数组名是一个地址,那么函数名也是一个地址
1.如何定义一个函数指针
跟普通变量一样,
int a;
int *p;
char c;
char *p;
int getData(int a, int b);//函数传参
int (*p)(int a,int b);//函数指针的调用
//如何使用函数指针
#include <stdio.h>
void printf_hello()
{
printf("欢迎来到我的世界\n");
}
int main()
{
printf_hello();//这是以前我们调用函数
void (*p)(); //1.定义一个函数指针
p = printf_hello; //2.指针指向函数
(*p)(); //3.把函数指针里面的内容调用出来
//函数调用概念和变量一样
return 0;
}
//OK,现在我有两个函数,类型不同,有两种访问的方式,1.直接访问(函数调用) 2.间接访问(函数指针)
//下面来着重介绍一下函数指针的用法
#include <stdio.h>
void printf()
{
puts("欢迎来到我的世界");
}
int idata(int dataone)
{
return ++dataone;
}
int main()
{
//1.定义指针 2.指针指向函数 3.调用函数指针
void (*p1)();
int (*p2)(int a);//注意实参也不要丢了啊啊啊啊(原先丢了一次编译会出错)
p1 = printf;
p2 = idata;
(*p1)();
printf("将P2呈现出来是%d\n",(*p2)(12));
return 0;
}
2.好用之处
根据程序运行过程的不同情况,调用不同的函数(Java接口)
练习题
#include <stdio.h>
int getMax(int a,int b)
{
return a>b?a:b;
}
int getMin(int a,int b)
{
return a<b?a:b;
}
int getSum(int a,int b)
{
return a+b;
}
int dataHandler(int a,int b,int(*p)(int ,int))//我们强调的是函数类型,而具体的形参名如果用不到的话可以不定义
{
int ret;
ret = (*p)(a,b);
return ret;
}
int main()
{
int a = 10;
int b = 20;
int cmd;
int ret;
//1.定义函数指针
int (*pfunc)(int ,int );
//2.根据你输入的值来决定指针指向那个函数
printf("请输入1(求大者),2(求小者),3(求和)\n");
scanf("%d",&cmd);
switch(cmd)
{
case 1: pfunc = getMax; break;
case 2: pfunc = getMin; break;
case 3: pfunc = getSum; break;
default:
printf("输入错误。@请输入1(求大者),2(求小者),3(求和)");
//exit(-1);
break;
}
//3.将函数指针调用出来
// ret = (*pfunc)(a,b);//这是一种简单的写法,还有一种封装函数的方法,了解一下
ret = dataHandler(a,b,pfunc);
printf("result of is %d",ret);
return 0;
}
回调函数的底层逻辑,
- 线程 int pthread_create(pthread_t *id,const pthread_attr_t attr, void(start_rtn)(void), void *restrict arg);
- QT的信号与槽
8.指针数组
1.定义,注意和数组指针的区别(面试会考)
简单来说就是,一个由指针组成的数组,
//数组指针的使用
#include <stdio.h>
int main()
{
int a,b,c,d;
a = 10;
b = 20;
c = 30;
d = 40;
int *p[4] = {&a,&b,&c,&d};
for(int i = 0;i<4;i++)
{
printf("%d\n",*p[i]);
}
return 0;
}
2.函数指针的使用
//函数指针数组
#include <stdio.h>
int getMax(int a,int b)
{
return a>b?a:b;
}
int getMin(int a,int b)
{
return a<b?a:b;
}
int getSum(int a,int b)
{
return a+b;
}
int main()
{
int a = 10;
int b = 20;
int cmd;
int ret;
//1.定义函数指针
//这个由上面得来,那我如果想要函数指针数组呢?
int (*pfunc[3])(int ,int ) ={getMax,getMin,getSum};
//3.将函数指针调用出来
for(int i = 0;i<3;i++)
{
ret = (*pfunc[i])(a,b);//这是一种简单的写法,还有一种封装函数的方法,了解一下
printf("result[%d] of is %d",i,ret);
}
return 0;
}
9.指针函数
一个返回值是指针的函数
概念
练习题:
#include <stdio.h>
int* get_pos_person(int pos,int (*p)[4])
{
int *p2;
p2 = (int*)(p+pos);//因为这两个变量的类型不一样,所以要强转一下
return p2;
}
int main()
{
int arr[3][4] ={{11,22,33,44},{15,23,54,84},{61,51,48,25}};
int pos;
int *ppos;
printf("请输入想看的学生号数(0,1,2):\n");
scanf("%d",&pos);
ppos = get_pos_person(pos ,arr);
for(int i =0;i<4;i++)
{
printf("他的成绩分别为%d\n",*(ppos++));
}
return 0;
}
10.二级指针
一级指针指向一个变量,存放他的地址;那么这个一级指针它本身也有一个地址,如果我再定义一个指针来存放这个一级指针的地址,那么这个指针就是二级指针,以此类推可延伸至三级乃至多级指针
认知考虑的时候,其实所有东西跟一级指针一样,写法:int **p;
#include <stdio.h>
int main()
{
int data = 100;
int *p = &data;
int **p2 = &p;//二级指针
printf("data的值为%d\n",data);
printf("data本身的地址为%p\n",&data);
printf("p存放的值为%d\n",*p);
printf("p存放的值为%p\n",p);
printf("p的地址为%p\n",&p);
printf("p2存放的值为%p\n",*p2);
printf("p2存放的值为%p\n",p2);
printf("p2的地址为%p\n",&p2);
printf("p2存放的地址的值(也就是一级指针存放的值为)%d\n",**p2);
return 0;
}
差别就是保存的是指针变量的地址。当你通过函数调用来修改调用函数指针指向的时候,就像通过函数调用修改某变量的值的时候一样
//1.如果我们不返回一个int型指针呢?只有一个空类型,应该怎么传参呢?(此代码为上面已用过的)
//2.当你通过函数调用来修改调用函数指针指向的时候,就像通过函数调用修改某变量的值的时候一样
#include <stdio.h>
//int* get_pos_person(int pos,int (*p)[4])
void get_pos_person(int pos,int (*p)[4],int **ppos)
{
//第一个括号是强转的意思。,转为指针变量
*ppos = (int *)(p+pos);//我们修改的ppos的地址,用一个二级指针来承接一级指针的地址然后改动一级指针的地址
}
int main()
{
int arr[3][4] ={{11,22,33,44},{15,23,54,84},{61,51,48,25}};
int pos;
int *ppos;
printf("请输入想看的学生号数(0,1,2):\n");
scanf("%d",&pos);
get_pos_person(pos ,arr,&ppos);
for(int i =0;i<4;i++)
{
printf("他的成绩分别为%d\n",*(ppos++));
}
return 0;
}
二级指针不能简单粗暴指向二维数组
11.总结
中小公司大概率考题