指针学习笔记

指针:指针是用来存放地址的变量。这个地址可以是变量的地址,也可以是数组、函数的起始地址,还可以是指针的地址。
注意:指针变量的值是它所指向的变量的地址,而不是该变量的值。
指针的定义格式:
类型名 指针名
其中类型名说明指针所指向变量的类型。星号(
)是一个指针变量的标志,说明指针名是一个“类型名”类型的指针而不是一个“类型名”类型的变量。指针可与非指针变量一起说明。
例如:
char *p,ch;
在上面的定义中,ch是一个char类型的变量,而p是一个指向char型变量的指针。而定义:
int p1;
p1也是一个指针,它是指向int型变量的指针。虽然p、p1都是指针,它们存放的都是内存的地址,但是 这并不意味着他们的类型相同。在使用中,p只能存放char型的地址;而p1只能存放int型的地址。指针类型定义的不同,是因为不同的类型的定义,有着不同的字节数量,指针加1是可以进行地址的移动,但是譬如int整型变量有四个字节,char字符型有一个字节,如果不定义指针变量的类型,就容易在指针的移动中,出现不必要的错误。
int
p和int p是一样的,可以认为()是和p在一起的。
注意:在使用指针之前,一定要先使指针有明确的指向。

与指针有关的运算符
(1)&:取地址运算符
(2):指针运算符(或者称“间接访问”运算符)。取其指向的内容
注意:(
)是命名的标志,有了(),后面的定义才是一个指针变量,没有(),则不是,它与p没有关系。
例如:&a为变量a的地址(p=&a=a(a为数组名)),*p为指针p指向的内存单元的内容(*p=a)

pc)++相当于c++。注意:括号在这里是必需的,如果没有括号,就成为pc++,而和++的优先级一样,自右向左结合,因此相当于(pc++),由于++在pc的右侧,是先使用
变量然后再加,因此先对pc的原值进行*运算,得到c的值,再使pc的值改变,这样 pc就不再指向c了。
指针可以做到不交换整型变量的值,只交换指针变量的值来达到设计目的。

指针与数组
指针是用来存放内存地址的变量,当我们定义一个指针,并且使这个指针存放数组中第一个元素地址时,就可以说是该指针指向了这个数组,这样我们就可以通过这个指针来访问数组中的元素了。
① int a[10];
② int p; //这里指针变量的类型必须与数组的类型一致
③ p=&a[10]; //该语句指向了数组的第0个元素的地址,即数组的首地址
④ p=a; //数组名就代表数组的首地址,此语句与p=&a[0];等价
⑤ int p=a //p=1 表示对p当前所指向的数组元素赋值为1;
指针变量的运算在使用时应十分小心,如果先使p指向数组a的首元素(p=a),请注意以下几点:
(1) p++。使p指向下一个元素,即a[1].若再执行
p,则得到a[1]的值。
(2) p++。由于++和同优先级,结合方向自右向左,因此它等价于
(p++)。先得到
p,即a[0],然后再使p的值加1.如例6-4中改写后的程序如下
For(i=0,p=a;i<10;i++,p++)
Printf(“%d “,p);
相当于
For(i=0,p=a;i<10;i++){
Printf(“%d “,p);
P++;
}
(3)
(p++)与
(++p)作用不同,前者是先取p的值,然后使p加1。后者是先使p加1,再取p的值。
(4)(*p)++表示 p所指向的元素值加1,若p的初值为a。则(*p)++相当于a[0]++,即若a[0]=2,则(*p)++=3.注意元素值加1,而不是指针值加一。
(5)如果p指向a数组中第i个元素,则
(p–)相当于a[i–],先对p作“”运算,再使p自减。
(++p)相当于a[++i],先使p自加,再进行“”运算。
(–p)相当于a[–i],先使p自减,再进行“”运算。

指向多维数组的指针
a[3][2]:
a是二维数组名,它应代表整个二维数组的首地址,也就是二维数组第0行的首地址,即2000.那么a+1就是我们把a看作一维数组时的下一个个地址,即二维数组第一行的首地址。
说明:
a[0],a[1],a[3]代表的是每一行的首地址
(1)、如果要输出地址值,可使用的表达形式:
以a[0](第0行首地址)为例,等价于:&a[0][0],(a+0);
以a[1][2](第1行第二个元素)为例,等价于:&a[1][2],a[1]+2,
(a+1)+2
以上表示形式的输出都是该元素位置的地址值。
(2)、如果要输出元素值,可使用的表达形式:
以a[1][1](第一行第一个元素)为例,等价于:(a[1]+1),((a+1)+1),a[1][1].
注意:在指向行的指针前面加上(
)变成指向列的指针;
在指向列的指针前面加上(&)变成指向行的指针。
在指针表示中,p=a不合法,p=a[0]合法。虽然两者在编程中可以输出,但是a是二维数组,a[0]在这里相当于是一维数组,虽然代表的都是这一整个数组的首地址 ,但是类型还是有所不同的。
【例】输出二维数组中的任意一个元素值
#include <stdio.h>
#include <stdlib.h>

int main()
{
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int (p)[4],i,j;
p=a;
scanf("%d %d",&i,&j);
printf(“a[%d][%d]=%d\n”,i,j,
(*(p+i)+j));
return 0;
}
程序中的“int(p)[4]”即表示一个指针变量,它指向包含4个元素的一维数组。注意不要遗漏了括号,如果写成int p[4],那么就成了指针数组了。在这里p有4个元素,每个元素是 整形。可以对比一下int a[4]的定义。P所指向的对象是有四个整型元素的数组,,即 p是行指针。此时p只能指向一个包含四个元素的一维数组,p的值就是该一维数组的首地址。P不能指向一维数组中的第i个元素。
我们怎样在一个二维数组这种输出一个指定的元素呢?
首先应知道起始位置,即数组的首地址,然后计算该元素在数组中的相对位置,计算 a[i][j]在数组中的相对位置的计算公式是(假设二维数组的列数为m):
i
m+j
C语言规定数组的下标从0开始,所以在计算相对位置时比较方便,只要知道i和j的 值,就直接可以利用上式计算地址。

指针数组和数组指针
指针数组:指针变量可以同其他变量一样作为数组的元素,如果一个数组的元素是由指针变量组成,那么这个数组称为指针数组。
指针数组的定义形式为
类型名 *数组名[常量表达式]
例:int *a[10];
(对比字符串指针:
字符串指的是在内存中存放的一串以“\0”结尾的若干个字符。
字符串的定义形式为:
char *str(指针名)=“hello world!;
等级于下面这两行:
char *str;
str=“hello world;)
int *a[3]不同于int(*a)[3],后者说明p是一个指向有3个int型元素的数组的指针。
指针数组的定义对字符串的处理提供了更大的方便和灵活,使用二维数组在处理长度不等的正文时效率低,而指针数组由于其中每个元素都为指针变量,因此通过 地址运算来操作正文行是十分方便的。
数组指针
数组指针的定义形式:
类型名 (*指针名)[整形常量]
例:int (*a)[10];
得到数组指针,数组指针又叫行指针。它是一个指向长度为整型常量的一维数组的指针。在上例中,就是指向长度为10的一维数组的指针。

指向指针的指针:
定义:指向指针数据的指针变量,简称为指向指针的指针,
int *p;
定义了一个指向整型数据的,用它可以存放整型数据的地址,并且用它可以对指向的变量进行间接的访问。进一步定义:
int **pa;
这里 pa就是指向指针数据的指针变量,它可以指向指针变量p,也就是存放指针变量 p的地址。
补充说明:利用一个指针变量访问另一个变量就是所谓的间接访问。如果在一个指针变量中存放一个目标 变量的地址,这就是“单级地址“,指向指针的指针用的是”二级地址“的方法。

动态内存分配:
C语言的动态内存分配函数主要有两个:
malloc()(内存分配,memory allocation)和calloc()(连续内存分配,contiguous allocation)
这两个函数在头文件stdlib.h中。
计算机在运行过程中,除了被系统和用户程序使用的内存外,还有一部分尚未使用短的内存空间,它就是堆(heap)。可以通过系统函数在堆上根据需要的大小来动态分配内存,而且堆的大小并不固定,可以动态扩张和缩减。
当程序调用 动态内存分配函数时,就会在堆中为程序 分配内存。如果堆中的内存不够,那么系统就会在空闲内存中继续分配新的内存,增加堆的容量,以满足需要。堆的容量会动态增加,直到所有的空闲内存全部用完。
malloc()和calloc()两个函数 都是用于在堆中动态分配内存的,但用法有所不同。
1、 malloc()函数:
函数原型:
void malloc(unsigned int num_bytes);
说明:该函数只有一个参数,就是要分配内存的总字节数。分配完成后,不会自动初始化。。
比如,malloc(10
4);
表示要求编译系统分配共40个字节的存储空间。
2、 calloc()函数:
函数原型:
void calloc(unsigned n,unsigned size);
说明:该函数有两个参数,第一个参数是需要分配多少个这种数据类型的数据,第二个参数是需要分配的数据类型所占的字节数。分配 完成后,被分配的数据被自动 初始化成该类型的数据的缺省值,数据 类型为0,字符型为“\0”。
比如,calloc(10,4);
表示要求编系统分配10个4字节的存储空间,并且会将这10个数自动初始化为0。
用malloc()和calloc()这两个函数 完成内存分配后,这两个函数 都会返回一个void
类型的指针,这个指针所指的位置,就是刚刚分配到内存块的首地址。
关于void类型,通过上述知识可以得到:
(1)、如果void出现在函数定义的参数中,那代表该函数没有参数;
(2)、如果void作为函数返回类型,表示该函数没有返回值;
(3)、如果void和*共同作为函数返回类型,则表示该函数返回一个通用类型的指针。在实际使用中,需要将这种通用类型的指针转化成为所需要的指针类型,然后再来使用。

动态内存分配完,当变量使用完动态内存之后,内存不会被自动回收,而且这些内存还不能再次使用,这样就会造成许多的内存黑洞,因此,必须主动收回这些内存,free()函数就是用于回收内存的。
注意 :问:
比如main函数里有一句 malloc(),
后面没有free()
1.那么当main结束后,动态分配的内存不会随之释放吗?
2.如果程序结束能自动释放,那么还加上free(),是出于什么考虑?
答:

  1. 就算没有free(),main()结束后也是会自动释放malloc()的内存的,这里监控者是操作系统,设计严谨的操作系统会登记每一块给每一个应用程序分配的内存,这使得它能够在应用程序本身失控的情况下仍然做到有效地回收内存。你可以试一下在TaskManager里强行结束你的程序,这样显然是没有执行程序自身的free()操作的,但内存并没有发生泄漏。
  2. free()的用处在于实时回收内存。如果你的程序很简单,那么你不写free()也没关系,在你的程序结束之前你不会用掉很多内存,不会降低系统性能;而你的程序结束之后,操作系统会替你完成这个工作。但你开始开发大型程序之后就会发现,不写free()的后果是很严重的。很可能你在程序中要重复10k次分配10M的内存,如果每次使用完内存后都用free()释放,你的程序只需要占用10M内存就能运行;但如果你不用free(),那么你的程序结束之前就会吃掉100G的内存。这其中当然包括绝大部分的虚拟内存,而由于虚拟内存的操作是要读写磁盘,因此极大地影响系统的性能。你的系统很可能因此而崩溃。
  3. 任何时候都为每一个malloc()写一个对应的free()是一个良好的编程习惯。这不但体现在处理大程序时的必要性上,更体现在程序的优良的风格和健壮性上。毕竟只有你自己的程序知道你为哪些操作分配了哪些内存以及什么时候不再需要这些内存。因此,这些内存当然最好由你自己的程序来回收。 -------摘自百度知道

例:用堆内存实现了动态数组t[n],数组元素的值是[0,100]之间的随机数
#include <stdio.h>
#include <stdlib.h>

int main()
{
int *t,i,n;
printf(“n= “);
scanf(”%d”,&n);
t=(int )malloc(nsizeof(int)); //动态分配的是地址
if(t==0){
printf(“无足够内存 “);
exit(1);
}
for(i=0;i<n;i++){
t[i]=rand()%101;
printf(”%4d”,t[i]);
}
free(t);
return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值