(1)指针的本质
指针的本质其实就是间接访问
A.指针的定义
:内存区域的每个字节都对应一个编号,这个编号就是“地址”,如果在程序中定义了一个变量那么
在对程序进行编译时,系统会给这个变量分配内存单元,按变量地址存取变量值的方法为“直接访问”,列入printf和scanf函数,而“间接访问”是将变量的地址存放到另一个变量中,在c中指针变量是一种特殊变量,它用来存放变量的地址
B.指针变量的定义格式
基类型 *指针变量变量名;
例如
int *i_pointer;
注意:
1,对于指针变量和间接访问的理解:
直接访问可以理解为直接把宝藏给你
间接访问则是将地址理解为藏宝图,给你藏宝图从而让你找到宝藏(多了一次跳转)
2,指针变量占八个字节(64位应用程序)而三十二位则为4个字节
3,只有指针变量可以储存地址
4,说某个变量的地址指的都是起始地址
5,指针变量和指针是两个概念,指针等同于为变量的地址,而指针变量是存储指针的变量
C.取地址操作符和取值操作符,指针本质
取地址操作符为&,也称为引用,通过这个操作符可以获取一个变量的地址值,取值操作符为*,也称为解引用,通过该操作符可以获得一个地址对应的数据
例
#include<stdio.h>
int main()
{
int i=0;
//定义一个指针变量i_pointer为指针变量名
//指针变量的初始化必须为某个变量取地址来复制,不能写随机数
int *i_pointer=&i;//定义指针变量与初始化
printf("直接访问的结果为%d\n",i);//直接访问
printf("间接访问的结果为%d",*i_pointer);//间接访问
return 0;
}
注意
1.指针变量前的*,表示该变量为指针变量,例如
float *i;
此时注意指针变量名为i不是*i
2.定义指针变量必须指定其类型,需要注意的是,只有整形变量的地址才能放到指向整型变量的指针变量中,如果定义的指针变量的基类型与实际赋值的地址的变量类型不同会报错,例如:
float i;
int *i_1;
i_1=&i;//会报错
3.如果已执行语句
pointer_1=&a;
那么&*pointer_1的含义是什么呢
&*两个运算符的优先顺序相同,但要自右向左结合,那么*pointer为a,&*pointer为&a
也就是pointer_1
&*a的含义为什么呢
与a等价(与上述证明思路相同)
4.定义指针变量时*与变量挨着的原因
int* i_pointer,a,b;会无法判断哪个为指针变量,所以将星号放到指针变量名前更清晰易于辨认
(2)指针的传递(使用场景)
指针的使用场景大致为两个,指针的传递和指针的偏移
接下来讲为什么使用指针的传递,例:
#include<stdio.h>
//在子函数内去改变主函数某个变量的值
void change(int j)
{
j=10;
}
int main()
{
int i=5;
printf("变化前%d\n",i);//5
change(i);
printf("变化后%d\n",i); //5
return 0;
}
而之所以i值没有改变,是因为c语言才用的是值传递,j是形参,i为形参,传递实参时实际上是语句j=i来传递i的值
那么change(i)的过程为
i=5,j=i,j=10
那么改变j本身不会改变实参i的值
即形参和实参的地址不同,无法直接修改
因此需要指针的传递来解决上述问题
要在子函数内在相同地址改变i的值
#include<stdio.h>
//在子函数内去改变主函数某个变量的值
void change(int *j)//形参是指针变量,上传的是a的指针,此时j=&a,则*j=a,地址相同,则对*j的修改就能直接改变a
{
*j=10;
}
int main()
{
int i=5;
printf("变化前%d\n",i);//5
change(&i);//上传a的地址
printf("变化后%d\n",i); //10
return 0;
}
(3)指针的偏移(应用场景2)
把对指针的加减称为指针的偏移
A.向后偏移
例
#include<stdio.h>
//指针的偏移 使用场景,也就是对指针进行加减
#define N 5
int main()
{
int a[N]={1,2,3,4,5};//数组名就储存了数组的起始地址,a中储存的就是一个地址值 a:int [5]
int *p;//调试的地址0x62fdf0,p+1地址为0x62fdf4
p=a;
int i;
for(i=0;i<N;i++)
{
printf("%3d",a[i]);
}
return 0;
}
由上图可知,地址p和地址p+1相隔的是数组类型占用的字节数,即偏移的长度为其基类型的长度,也就是偏移sizeof(int),这样可以通过*(p+i)得到元素a[i],则将上述代码改为以下:
#define N 5
int main()
{
int a[N]={1,2,3,4,5};//数组名就储存了数组的起始地址,a中储存的就是一个地址值 a:int [5]
int *p;//调试的地址0x62fdf0,p+1地址为0x62fdf4
p=a;
int i;
for(i=0;i<N;i++)
{
printf("%3d",*(p+i));//p的值对应数组第一个,可根据偏移遍历数组内的所有元素
}
return 0;
}
结果不变
对上述代码扩展,
B.尝试向前偏移
#include<stdio.h>
//指针的偏移 使用场景,也就是对指针进行加减
#define N 5
int main()
{
int a[N]={1,2,3,4,5};//数组名就储存了数组的起始地址,a中储存的就是一个地址值 a:int [5]
int *p;//调试的地址0x62fdf0,p+1地址为0x62fdf4
p=a;
int i;
for(i=0;i<N;i++)
{
printf("%3d",*(p+i));//p的值对应数组第一个,可根据偏移遍历数组内的所有元素
}
printf("\n----------\n");
p=&a[4];
for(i=0;i<N;i++)
{
printf("%3d",*(p-i));//p的值对应数组第四个,可根据偏移遍历数组内的所有元素
}
return 0;
}
C. 指针和一维数组的传递(字符数组)
#include<stdio.h>
//数组名作为实参传递给子函数时,是弱化为指针的
//练习传递与偏移
void change(char *d)
{
*d='H';//将d[0]改为H
d[1]='E';//d[1]与*(d+1)等价
*(d+2) ='L';//d[2]
}
int main()
{
char c[6]="hello";
change(c);
puts(c);
return 0;
}
(4)指针与动态内存的申请//可根据这个实现长度不固定数组的应用
c语言的数组长度固定是因为定义的整形,浮点型,字符型变量,数组变量都在栈空间,而栈空间的大小在编译时是确定的,如果使用的空间大小不确定,则需要堆空间。例:
#include<stdio.h>
#include<stdlib.h>//malloc所使用的头文件
#include<string.h>
int main()
{
int size;//size代表要申请的内存大小
char *p;
scanf("%d",&size);//输入要申请的内存大小
//malloc返回的void*代表无类型指针,需要强制转换,否则编译器会警告
//可以理解为向校长申请地皮盖房子,要怎么使用取决于你的要求,因此无类型。
//要强制转换的原因:void*的指针无法偏移,会报错,因此需要强制转换,因此基本不定义无类型指针
p=(char*)malloc(size);
strcpy(p,"paladin");
puts(p);
free(p);//free当你不用了就需要释放申请的空间 ,不能改变p例如
//free(p+1)会报错
return 0;
}
注意
1.要注意指针本身大小和它指向的空间大小是两码事,不能和前面的变量类比去理解
2.堆的效率要比栈低的多,优势是可以用动态申请
习题1:
#include<stdio.h>
#include<stdlib.h>//malloc所使用的头文件
#include<string.h>
void change(int *c)
{
*c=*c/2;
}
int main()
{
int i;
scanf("%d",&i);
change(&i);
printf("%d",i);
return 0;
}
#include<stdio.h>
#include<stdlib.h>//malloc所使用的头文件
#include<string.h>
int main()
{
int size,*p;
char c;
scanf("%d",&size);
scanf("%c",&c);//移除缓冲区的\n
p=(char*)malloc(size);//申请为空间为size的p
gets(p); //输入
printf("%s",p);
return 0;
}