指针 -- 地址
指针就是地址,地址就是指针
& -- 取地址运算符
* 取值运算符 (解引用)
得到地址 -- 要访问他的值 -- * 取值运算符 -- 对地址解引用
a=*(&a)
----------------------------
指针变量 -- 存放指针的变量
初始化方式 -- int *p;
int a=10;
int *p=&a;
printf("%p\n",&a);
printf("%d\n",*(&a));
printf("%p\n",p);
printf("%d\n",*(p));s
访问变量的两种方式:
1. 直接访问 -- 用变量名
2.间接访问 -- 将变量地址存放到一个指针变量中,再通过 * 取值间接访问变量
定义指针的时候要求变量类型要一致 -- int 类型的数据就要用 int * 类型的指针来存放 不然可能会出现数据丢失情况
such as: 使用char * 来存放打 int类型变量 的 地址 ,当用 * 取地址的时候 char -- 一个字节--8bit 位 取不完4字节32bit的数据
不同类型指针的增加步幅也不一样 -- int *pi; char *pc; 这里++pi的一次增加4bit的地址数据,而pc一次增加1bit的地址数据
//验证程序:
#include<stdio.h>
int main()
{
int a=0x1234;
int *p=&a;
char *pc=&a;
puts("& 和 * 的用法演示:");
puts("使用a取地址和值:\n");
printf("%p\n",&a);
printf("%d\n",*(&a));
puts("使用指针p取地址和值:\n");
printf("%p\n",p);
printf("%d\n",*(p));
puts("不同类型指针的演示;");
printf("int 地址-- %p\n",p);
printf("char 地址-- %p\n",pc);
printf("int -- 数值 0x%x\n",*p);
printf("char -- 数值 0x%x\n",*pc);puts("自增差异");
printf("int -- 0x%p\n",++p);
printf("char -- 0x%p\n",++pc);
return 0;
}test1:实现 一个交换函数 -- 要求交换两个变量的值
#include<stdio.h>void sswap(int *a,int *b)
{
int t=*a;
*a=*b;
*b=t;
}
int main()
{
int a=3;
int b=4;
printf("交换前\na= %d\nb= %d",a,b);
sswap(&a,&b);
printf("交换后\na= %d\nb= %d",a,b);
return 0;
}
-------------------------------------------------------------------------
volatile对应的变量可能在你的程序本身不知道的情况下发生改变
比如多线程的程序,共同访问的内存当中,多个程序都可以操纵这个变量
你自己的程序,是无法判定何时这个变量会发生变化
还比如,他和一个外部设备的某个状态对应,当外部设备发生操作的时候,通过驱动程序和中断事件,系统改变了这个变量的数值,而你的程序并不知道。
对于volatile类型的变量,系统每次用到他的时候都是直接从对应的内存当中提取,**而不会利用cache当中的原有数值,**以适应它的未知何时会发生的变化,系统对这种变量的处理不会做优化——显然也是因为它的数值随时都可能变化的情况。
指针的第二个场景 -- 指向 固定的地址 -- 单片机 armbootloader -- 会用到
volatile unsigned int *p=(volatile unsigned int *)0x000000000061FE2B;
test
-- 输入3个数,要求封装一个函数实现输出的时候他们是从大到小
#include<stdio.h>
void mySwap(int *a,int *b)
{
int t=*a;
*a=*b;
*b=t;
}
void mySort(int *a,int *b,int *c) //使用指针排序后赋值回去
{
// ab ac bc -- abc
if(*a<*b)mySwap(a,b);
if(*a<*c)mySwap(a,c);
if(*b<*c)mySwap(b,c);
}
int main()
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
mySort(&a,&b,&c);
printf("%d %d %d\n",a,b,c);
return 0;
}
通过指针引用数组 -- 传参是 -- 数组首地址p=arr;
p=&arr[0];
指针的增量与数组的关系#include<stdio.h>
int main()
{
int arr[3]={1,2,3};
int *p;
//p=arr;
p=&arr[0];
// printf("首元素:%d\n",*p);
// printf("1元素:%d\n",*(p+1));
// printf("2元素:%d\n",*(p+2));
for(int i=0;i<3;++i)
{
printf("下标:%d 的元素为%d\n地址:0x%p\n",i,*(p+i),p+i);
}
return 0;
}
访问数组的两种方式 :
1.常用 下标 优点 -- 可读性差
2. 指针 -- 优点: 高效
使用指针遍历数组 用完之后记得 重新赋值指针 不然给后面再访问的时候会数组越界
p=arr; --- 重新回到数组的首元素
for(int i=0;i<3;++i)
{
printf("%d ",*p++);
}
puts("");
p=arr;
for(int i=0;i<3;++i)
{
printf("%d ",*p++);
}
puts("");
p=arr;
-------------------------------------------
数组名 和 指针的混用:
可行: 都是指针(地址)
1.p[n] -- 指针当做数组名,下标访问
// 觉得奇怪可以 想象 函数传入指针作为参数去访问数组的时候也是这么用的
such as:
void func(int *arr)
{
arr[0]=1;
}
2.数组名拿来 操作 -> *arr==arr[0], arr +1=arr[1]
#include<stdio.h>
int main()
{
int arr[3]={1,2,3};
int *p;
p=arr;
for(int i=0;i<3;++i)
{
printf("%d ",p[i]);
}
puts("");
for(int i=0;i<3;++i)
{
printf("%d ",*(arr+i));
}
return 0;
}
不可行:(指针变量可以改变,指针常量不可修改)
指针 -- 指针变量
数组名 -- 指针常量 -- 数组名确定数组开头位置 不能更改
不能 ++ , *arr++ x
但是可以通过+i访问 *(arr+i) √
sizeof 的差别
sizeof(arr) -- 数组大小 比如 int[3] = 4*3=12bit
sizeof(p) -- 操作系统用 8个字节表示一个指针(地址)
//注意:只要是 指针就是 8字节大小 sizeof(int*0 和 sizeof(char*)大小一样
int arr[3]={1,2,3};
int *p;
p=arr;
char *pc=arr;
printf("arr sizeof :%d\n",sizeof(arr));
printf("p sizeof :%d\n",sizeof(p));
printf("pc sizeof :%d\n",sizeof(pc));gcc test.c -g
// -g -- gdb 调试(debug)
//进入debug 模式 后 r --running运行 q --退出
//注: 使用函数对数组操作 -- 完成后不需要赋初值了,因为函数每次调用都对形参赋初值
在 scanf 中,%d 和 %i 的行为不同
%d 假设基数为10,而 %i 自动检测基数。因此,两个说明符在与输入说明符一起使用时的行为不同。对 %i 而言,012是10;对 %d 而言,012就是12。
%d 取整数值作为有符号十进制整数。它接受负值和正值,但值应为十进制,否则将打印垃圾值。(注意:如果输入是八进制格式,如012,那么 %d 将忽略 0 并将输入视为 12)考虑以下示例。
%i 取十进制、十六进制或八进制类型的整数值。要输入十六进制格式的值 - 值前面应添加“0x”,输入八进制格式的值 - 值前面应添加“0”。
指针 常见错误:
段错误 -- Segmentaion fault -- 一般都是 野指针
test :
使用指针初始化函数来初始化数组:
#include<stdio.h>
void initArr(int *parr,int len)
{
int i;
for(i=0;i<len;++i)
{
printf("请输入第%i个数:",i+1);
scanf("%d",&parr[i]);
}
}
void printArr(int *parr,int len)
{
int i;
for(i=0;i<len;++i)
{
printf("%i ",parr[i]);
}
puts("");
}int main()
{
int arr[5];
int len =sizeof(arr)/sizeof(arr[0]);
initArr(arr,len);
printArr(arr,len);
return 0;
}test2 -- 数组翻转
#include<stdio.h>
void initArr(int *parr,int len)
{
int i;
for(i=0;i<len;++i)
{
printf("请输入第%i个数:",i+1);
scanf("%d",&parr[i]);
}
}
void printArr(int *parr,int len)
{
int i;
for(i=0;i<len;++i)
{
printf("%i ",parr[i]);
}
puts("");
}
void swap(int *a,int *b)
{
int t=*a;
*a=*b;
*b=t;
}void rollArr(int *parr,int len)
{
int i;
for(i=0;i<len>>1;++i)
{
swap(&parr[i],&parr[len-1-i]);
}
}int main()
{
int arr[6];
int len =sizeof(arr)/sizeof(arr[0]);
initArr(arr,len);
puts("数组翻转前");
printArr(arr,len);
rollArr(arr,len);
puts("数组翻转后");
printArr(arr,len);
return 0;
}
----------------------------
二维数组
基础认知:
二维数组的地址:
a[3][4]
a -- 父数组
a[0] a[1] a[2] -- 子数组 ,每个里面有4个元素
偏移量
arr+1 -- 从a[0]->a[1] --4* int=16 -- 行
a[0]+1 --从a[0][0]->a[0][1] --1*int =4 -- 列
数组首地址 2 种表达;
1.数组名也是数组元素的首地址--arr
2.数组第一个元素的地址--&arr[0]
a[1] -- &a[1][0]
对二维数组解引用 -- *a = a[0] --第一行的元素的数组
a[i] 等效于 *(a+i)
#include<stdio.h>int main()
{
int arr[3][4]={{11,22,33,44},{12,13,14,15},{44,55,66,77}};
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+0),*(arr+0)+1);
return 0;
}
#include<stdio.h>int main()
{int i,j;
int arr[3][4]={{11,22,33,44},{12,13,14,15},{44,55,66,77}};
for(i=0;i<3;++i)
for(j=0;j<4;++j)
{
printf("address:%d\tdata:%d\n",&arr[i][j],arr[i][j]);
printf("address:%d\tdata:%d\n",arr[i]+j,*(arr[i]+j));
printf("address:%d\tdata:%d\n",*(arr+i)+j,*(*(arr+i)+j));
printf("=================================================\n");
}
return 0;
}
总结:以下三种表达方式等效
&arr[i][j],arr[i][j]
arr[i]+j,*(arr[i]+j)
*(arr+i)+j,*(*(arr+i)+j) // 从上一步: arr[i]= *(arr+i)
表格总结
===================================
表示形式 含义 地址
a 二维数组名,0行首地址-a[0] 2000
a+1 &a[1] 1行首地址 2016
a[0] *(a+0) *a 0行0列元素地址 2000
a[1] *(a+1) 1行0列元素地址 2016
a[1]+2 *(a+1)+2 &a[1][2] 1行2列元素地址 2024
*(a[1]+2) *(*(a+1)+2 ) a[1][2] 1行2列元素的值 上表元素值13===================================
数组指针:
产生背景:
对于二维数组 ,如果我们想用指针遍历他的所有元素:
1. int *p=&arr[0][0]; *p++;--- 因为二维数组也是连续的内存空间,一直往后找即可
2. int *p=arr; *p++; -- 不推荐使用 ,会出发warning,因为虽然arr是第一行首地址,但arr+1 会产生歧义 无法做到一次跳一个子数组
概念: 定义一个指针,指向一个数组
如果我们需要实现arr++ 我们可以使用数组指针
int (*p)[4] = arr; //这时候才不会报错
#include<stdio.h>
int main()
{int i,j;
int arr[3][4]={{11,22,33,44},{12,13,14,15},{44,55,66,77}};
//int *p=&arr[0][0];
//int *p=arr;
int (*p)[4]=arr;for(i=0;i<3;++i){
for(j=0;j<4;++j)
{
printf("%d ",arr[i][j]);
}
puts("");
}
return 0;
}
===================================
二维数组 传参 -- 传的是数组指针 int (*p)[4]
test
给下标,找出二维数组的数
#include<stdio.h>void tipsInput(int *line,int *row)
{
puts("请输入你要查找的行列号:");
scanf("%d%d",line,row);
}int getResult(int (*p)[4],int line,int row)
{
//return p[line][row];
return *(*(p+line)+row);
}
int main()
{
int line,row,data;
int arr[3][4]={{11,22,33,44},{12,13,14,15},{44,55,66,77}};
//输入提示
tipsInput(&line,&row);
//找到对应的数
data=getResult(arr,line,row);
//输出 -- 打印
printf("第%d行%d列的数是:%d\n",line,row,data);
return 0;
}
===================================
函数指针
概念:
也叫函数地址 , 如果程序中定义了一段函数,编译系统为函数代码 统一分配一段存储空间,
这段存储空间的起始地址(入口地址)称为这个函数的指针
如何定义: int (*p)(int a,intb) -- 第二个()的参数根据需要自行选择
//为什么需要 第一个() -- 因为() 的优先级高于 *,我们要定义指针变量就得先()
类比:
变量的两种访问方式:
1.直接访问 -- 变量名
2.间接访问 -- 指针函数的两种访问方式:
1.直接访问 -- 函数名
2.间接访问 -- 指针指针优点:=根据程序执行的不同情况,调用不同的函数 -- 类似java 接口
test:
#include<stdio.h>
void printW()
{
printf("Hello World!\n");
}
int increaseF(int data)
{
return ++data;
}int main()
{
void (*p1)()=printW; //定义函数指针 指向函数
int (*p2)(int data)=increaseF;
(*p1)(); // 利用函数指针调用函数
printf("含参数的函数指针测试%d\n",(*p2)(100));
return 0;
}//注意: 如果函数体里面用不到参数,可以只写类型,不写参数名(int a)->(int)
线程里面的回调函数 -- 底层就是用函数指针实现
输入1,2,3 分别代表求 a,b的max,min,和
思路 -- 考察函数指针 处理多个函数 的不同情况 3中情况的 返回类型 和参数 类型相同,全放到函数指针操作即可
#include<stdio.h>
#include<stdlib.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 dataHandle(int a,int b,int (*pfun)(int,int))
{
return (*pfun)(a,b);
}
int main()
{
int t,a,b;
int (*pfun)(int a,int b);
puts("请输入两个数字:");
scanf("%d%d",&a,&b);
puts("请选择你的操作:(1.求最大值 2.求最小值 3.求和)");
getchar();
scanf("%d",&t);
switch(t)
{
case 1:
pfun=getMax;
break;
case 2:
pfun=getMin;
break;
case 3:
pfun=getSum;
break;
default:
puts("错误输入,请重新输入");
exit(-1);
break;
}
int res=dataHandle(a, b,pfun);
printf("处理结果:%d\n",res);
return 0;
}
===================================
指针数组
概念:
存放元素类型为指针的数组
定 义 int *p[4];
进化 -- 函数指针数组 int (*pfun[3]) (int ,int) -- 第一个 () 决定类型
指针如果没有初始化 -- 得到的就是野指针
int t,a,b;
int (*pfun[3])(int a,int b)={getMax,getMin,getSum};
puts("请输入两个数字:");
scanf("%d%d",&a,&b);
puts("请选择你的操作:(1.求最大值 2.求最小值 3.求和)");
for(t=0;t<3;++t)
{
printf("%d\n",(*pfun[t])(a,b));
}
===================================
指针函数 :
概念;
返回指针值的函数: int *fun(int a,int b)
解读:
int *p; -- int 类型的指针变量
int* p; -- 定义一个变量 类型是 int*
指针强调 : 起始地址 和 偏移值
===================================
二级指针
套娃 --指针套指针 -- 一次拿不到 data,还要接着访问的时候就 多 *一次
#include<stdio.h>
int main()
{
int data=10;
int *p=&data;
printf("data的地址:%p\n",&data);
printf("p保存data的地址:%p 内容是:%d\n",p,*p);
//想要通过p访问data
int **p2=&p;
printf("p2保存的是p的地址:%p 内容*p2是%p\n",p2,*p2);
printf("**p2访问data的数值:%d\n",**p2);
int ***p3=&p2;
printf("p3保存的是p2的地址:%p 内容*p3是%p\n",p3,*p3);
printf("***p3访问data的数值:%d\n",***p3);
return 0;
}// 类似 swap 的test:
#include<stdio.h>
void pSwap(int *p)
{
p=(int *)0x000000000061FE2B;
printf("%p\n",p);
}int main()
{
int a=100;
int *p;
pSwap(p);
printf("%p\n",p);
return 0;
}
//我们发现 得到的 指针p不一样, 因为p是作为形参传进去不能直接改变他的值
if 要修改值,我们可以:
1. 传入 int **p2 -- 传实参 -- 要修改他就传递他的地址进来
2.return int* 类型 让他接收
#include<stdio.h>void pSwap(int **p2)
{
*p2=(int *)0x000000000061FE2B;
printf("2 %p\n",*p2);
}
int* ppSwap(int *p2)
{
p2=(int *)0x000000000061FE2B;
return p2;
}
int main()
{
int a=100;
int *p;
p=ppSwap(p);
printf("%1 %p\n",p);
pSwap(&p);
printf("3 %p\n",p);
return 0;
}
===================================
二级指针和二维数组 差异:
二维数组 等价的指针是 数组指针 int arr[][4] -- int (*p)[4];
不能 简单的 将 二级指针 指向 二维数组:
such as: int arr[3][4]; int **p=arr;
// 这样只是将arr 的地址 给到 二级指针 p,但是*p,不等价于 *arr,同样 **p也不能修改
if 非要操作 ,要以 数组指针作为桥梁:
int (*p1)[4]=arr; int **p2=&p1; 这个时候 **p2就可以操作data了
#include<stdio.h>
int main()
{
int arr[3][4]={
{55,66,77,88},{66,55,34,89},{23,55,78,82}
};
int (*p1)[4]=&arr;
int **p2=&p1;
**p2=123;
printf("%d\n",arr[0][0]);
return 0;
}
===================================
指针 检验:
1.一个整形数: int a;
2.一个指向整形的指针: int *a;
3.一个指向指针的指针,他指向的指针指向一个整形数: int **a;
4.一个有10个整数的数组: int a[10];
5.一个有10个指针的数组,每个指针都指向一个整形数: int *a[10];
6.一个指向有10个整形数的数组的指针: int (*a)[10];
7.一个指针的指针,被指向的指针指向有10个整形数的数组: int (**a)[10];
8.一个指向数组的指针,该数组有10个整形指针: int *(*a)[10];
9.一个指向函数的指针,该函数有一个int参数,返回值也是int -- int (*p)(int)
10.一个有10个指针 的数组 ,每个指针指向一个函数 ,该函数有个整形参数,并返回一个整形数 int (*p[10])(int);
11.一个函数的指针,指向的函数类型有两个整形参数并且返回一个函数指针的函数,
返回函数指针指向有一个整形参数放回整形数的函数 -- int (* (*a)(int,int))(int);