【指针学习笔记】

指针

学习指针前需要知道的一些小基础

不同的数据类型和不同的变量的计算机内都是以二进制的形式储存的,当我们在声明一个变量的时候,计算机会自动分配一部分空间,而具体分配多大的空间取决于数据类型以及编译器。像:
int–>4 bites;
char–>1 bite;
float–>4 bites;
打个比方,这意味着每个变量都是被分配好位置的书籍,每本书的厚度(页数)不同,想要对书籍内储存的内容进行修改则需要知道书架的编号,即知道地址。

int a; //假设计算机内部为a规划了203–>207的空间,即此时a的地址为203
a=10; //此时对a做赋值运算,计算机会先找a的地址,即计算机内部标号为203的空间,在根据a的数据类型为int而对203–>207的空间内储存的内容进行更改
//这以为这此时203–>207内的数据为10(二进制形式储存)

简单来说,我们通过编程软件对某变量下达的指令,在计算机内部的执行都是先找到其地址,然后改变该地址内储存的值。

指针的基本介绍

指针是一个变量

指针是一个变量,在为该变量划分出来的空间内存放着另外一变量的地址,即给变量的值为地址值。

int a=10;
int *p; //将*放在变量前,意味着它后面的这个p是一个可以存放整型变量地址的变量
p=&a; //&放在变量前面,意味着取该变量的地址
//对p进行赋值,把a变量的地址值给到p变量所分配到的地址储存起来

假设a的地址为204,p地址为64,则:

p=204
&a=204
&p=64
*p=10 //在这里*表示取用该地址内存放的值(叫做引用)–>p此时因为已经做过赋值,p现在是a的地址值,所以*取用的是a的地址内存放的数据,也即是a的值->10

因为指针是一个变量,所以我们大多数时间对一般变量可以下达的指令对指针也成立,比如赋值,运算等等,只不过因为指针有它自己的规则,所以需要同一般变量区分开来。

对指针一些简单使用的理解

解引用

int a;
int *p;
p=&a;
printf(“%d”,*p);

在上面的过程中,我们完成了对p的初始化,使它指向了a的地址,但由于我们并没有对a进行初始化,而*很尽职地读取了a地址里存的东西,所以这个时候 *p的值会为为a分配的空间内存在的一些随机值。

既然*可以直接取用地址里存放的数值,所以我们可以通过*p来改变它所指向的a的值,p所指的地址(a分配到的地址)内存放的值被改变(叫做解引用)。

指针运算基础

p+1;//假设p所指的a地址为2002,则p+1==2006

既然p是指针变量,存放的是地址,那么p+1意味找下一本书了,而a的类型为int,如果理解为a着本书有4页,第一页标号为2002,那么下一本书第一页标号显然是2006而非2003。

一点易误导小贴士

int a=10;
int *p;
p=&a;

int a=10;
int *p=&a;

这里上下两段指令的效果相同,它们之间是等价的。需要注意的是在下面的这段指令中是对p赋初值而非*p。相比之下上面的更不容易引发误导。

在c语言中int* p和int *p没有区别。

*的一点运用–指向指针的指针

有了前面的基础,我们在学习指向指针的指针就只需要再多一条要点:一个*只对应一级取地址关系。
结合下面的例子理解一下

int x=5;
int *p;
p=&x;
*p=6;
int **q;
q=&p;
//假设x地址为225,p为215,q的地址为205
//因为一个*只取一次该地址内存放的值,q里放的值为p的地址,则*q会取到p的地址里存的值,即x的地址,这时**q相当于*(*q),也即*p ,即x。

以此类推,再多的*都是这样解决的。

传引用和函数传值

#include<stdio.h>
void jiayi(int a)
{
a=a+1;
}
int main(){
int a=10;
jiayi(a);
printf(“a=%d”,a);
}

上面是一个很简单的有毛病的代码,之所以说它有毛病是因为它并不能完成我们输出a+1的值的预期。
在这里,在一个函数里面声明的变量叫局部变量,它的生命周期只能维持在该函数的运行期间。也就是说,jiayi函数和main函数里面的a变量虽然同名,但它们的地址并不在同一个地方,也就是说我改a(jiayi)并不改变a(main)的值。当在main函数中调用jiayi函数时,只是把a(main)的值拷贝过去了(叫做函数传值)。

现在我们来更改一下上面的代码:

#include<stdio.h>
void jiayi(int *a)
{
*a=*a+1;
}
int main(){
int a=10;
jiayi(&a);
printf(“a=%d”,a);
}

在main函数中我们直接把a的地址丢给了jiayi函数愉快的玩耍,相当于jiayi函数直接偷家,摸到a的地址对里面存放的值做了更改,自然而然的我们在输出a的值时就是我们所预期的改变了值了。像这样通过指针在函数中对变量值改变的叫做传引用。
(函数传值只是拿笔记本抄书,原书内容不改变;而传引用就是直接在书上做笔记,书的内容当然会改变。)

指针类型

指针是强类型的

指针是强类型的:需要特定的指针变量来存放特定类型变量的地址
之所以是强类型是因为不同数据的存放空间大小和存放方式不同,所以通过指针存地址时就需要明确这些要素以便解引用时能够如同期望那般对这些地址存放的值进行更改。

void型指针(通用指针,泛指针,万能指针)

void指针为无类型指针,可用void指针来代替其他类型指针。
void指针可以指向任意数据类型,任何类型的指针都可以赋值给void指针,无需进行相知类型转换。

指针和数组

数组是一种特殊的指针

int A[5];
int*p;
p=A;

这样写是合理的,因为A就是A[0]的地址,A+1则是A[1]……以此类推。相同的*A=A[0],*(A+1)=A[1]……

数组作为函数参数

#include<stdio.h>
int zonghe(int A[],int size){
int i,sum=0;
for(i=0;i<size;i++){
sum+=A[i];
}
return sum;
}
int main(){
int A[]={1,2,3,4,5};
int size=sizeof(A)/sizeof(A[0]);
int total=zonghe(A,size);
printf(“%d\n”,total);
}

这是一个一点毛病没有的代码,但假如我们把它改成下面这个样子,希望在zonghe函数中直接计算数组的大小。

#include<stdio.h>
int zonghe(int A[]){
int i,sum=0;
int size=sizeof(A)/sizeof(A[0]);
for(i=0;i<size;i++){
sum+=A[i];
}
return sum;
}
int main(){
int A[]={1,2,3,4,5};
int total=zonghe(A);
printf(“%d\n”,total);
}

很遗憾,运行出现了问题,最后的输出值变成了1。
看一眼原因:
在zonghe函数中,传进去的A[]并不是将整个数组拷贝进去(因为数组可以很大,次次这么干太浪费内存了),而是将A[]看作一个指针,传进zonghe函数的是A的首地址,后面根据指针运算一步一步摸到要用数据的地址。所以在下面的代码中sizeof(A)是一个指针的大小,for循环只进行了一次就停止了,所以sum会为A[0]。

指针和多维数组(以二维数组为例)

多维数组实际上是数组的数组,数组可以理解为同类事物的集合。
n维数组实际上是创建了n个(n-1)数组。
而数组是一种特殊的指针,所以这里和前面的指向指针的指针有些关系(逻辑也差不多)。

int B[2][3];

如果我们写出int*p=B;这是不合理的,因为B返回的是一个指向一维数组(其中包含3个整型元素)的指针。

B==&B[0];
*B==B[0]==&B[0][0];
B+1==&B[1];//从B的首地址开始,前往下一个一维数组(指针的类型是很重要的!!!)
*(B+1)==B[1]==&B[1][0];
*(B+1)+2==B[1]+2==&B[1][2];
*(*B+1)==B[0][1];

在二维数组中,如果B为二维数组名,则B[i][j]==*(B[i]+j)==*(*(B+i)+j)

指针和动态内存–栈vs堆

内存是计算机的很重要的资源。
分配给应用程序的内存一般分为4部分:
Code(Text)(代码段)用来存放需要执行的指令,Static/Global(静态/全局数据段)用来存放静态或者全局变量(也就是不在函数声明的变量,它们的生命周期贯穿整个应用程序运行时段),Stack(栈区)用来存放函数调用的所以信息和所有的局部变量,Heap(堆)–内存空闲存取区。
前3个区段的大小在程序一开始就确定好了,比如栈区,在程序运行的时候不能申请更多的栈区,但确实需要更大的内存的时候我们就可以申请使用堆。

c语言中使用动态内存所要用到的函数

malloc

void* malloc(size_t size)//size_t约等于unsigned int;

int a;
int*p;
p=(int*)malloc(sizeof(int));//嘿,哥们,给我一块这么大的空间
//然后malloc会在堆上申请这样的一块空间并返回一个指向该内存起始地址的指针
//malloc返回的是一个void类型的指针
//malloc分配完内存后不会对其初始化

calloc

void* calloc(size_t num,size_t size);//num是特定类型元素的数量,size是类型的大小;
calloc和malloc差不多,只是它会对分配后的进行初始化–赋0;

realloc

void* realloc(void*ptr,size_t size);//ptr是指向一分配内存的起始地址的指针,size是新的内存块的大小;
当你有一块动态分配的内存,像要修改内存块的大小就可以用realloc;

free

用完要记得还,堆上的内存并不会自动释放,每当你用malloc申请动态内存时都是找一块空闲的给你。
每当用完了动态内存要记得还!!!

指针和函数

传引用

见前文

函数返回指针

#include<stdio.h>
#include<stdlib.h>
int *add(int*a,int*b){
int c=(*a)+(*b);
return &c;
}
int main(){
int a=2,b=4;
int *ptr=add(&a,&b);
printf(“%d\n”,*ptr);
}

试着多次运行一下这个代码,有概率对也有概率错。
这是因为计算机内部栈区对内存分布的规则导致的,当下运行的函数总在栈顶,当它运行完则释放这部分内存,堆在栈底的函数还在等栈顶的返回值,所以栈底的变量的地址不会改变,但栈顶就不一样了。
所以在函数返回指针时要注意,只能由栈底传向栈顶而不能反过来。
但考虑到底栈顶还是栈底有一些麻烦(起码我很烦),所以我们可以运用一下堆,从动态内存池里面申请一个内存,而这块内存的释放由我们控制,所以问题迎刃而解哩。
修改代码如下:

#include<stdio.h>
#include<stdlib.h>
int *add(int*a,int*b){
intc=(int)malloc(sizeof(int));
*c=(*a)+(*b);
return &c;
free(*c);
}
int main(){
int a=2,b=4;
int *ptr=add(&a,&b);
printf(“%d\n”,*ptr);
}

函数指针

函数指针是用来储存函数的地址的。
指针是指向后引用内存中的数据,这里的数据不一定是变量也可以是常量。
我们可以用指针储存和解引用,那么函数指针意味着我们也可以对函数这么做。

函数地址

函数其实是由一条条指令够成的,我们可以把一个函数理解为一个指令数组。那么接下来就和数组于指针关系的理解没有太大的区别了。
我们代码里敲的指令都会传到计算机内存中一个叫Code(Text)的片区,每条指令都会有一个地址,而函数的第一条指令的地址就是函数的地址。

怎么定义函数指针

#include<stdio.h>
int add(int a,int b){
return a+b;
}
int main(){
int c;
int (*p)(int,int);//因为add函数返回值为int,所以定义函数指针也为int,()内为*加上指针名,再跟一个()里面则是全部函数的参数类型
//不加()会变成指针函数
p=&add;//把add函数的地址赋给函数指针p
c=(*p)(2,3);//使用*解引用来获得这个函数,然后把两个参数传给这个函数
printf("%d,c);
}

函数指针可应用场景(待完善)

可用于回调函数
(这个太复杂了,暂时吃不下,之后再补)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值