关于指针进一步的知识点

一:函数指针和指针函数

1、【函数指针】

在程序运行中,函数代码是程序的算法指令部分,它们和数组一样也占用存储空间,都有相应的地址。可以使用指针变量指向数组的首地址,也可以使用指针变量指向函数代码的首地址,指向函数代码首地址的指针变量称为函数指针。

1、函数指针定义

函数类型(指针变量名)(形参列表);
“函数类型”说明函数的返回类型,由于“()”的优先级高于“
”,所以指针变量名外的括号必不可少,后面的“形参列表”表示指针变量指向的函数所带的参数列表。
例如:

int (*f)(int x);
double (*ptr)(double x); 在定义函数指针时请注意:

函数指针和它指向的函数的参数个数和类型都应该是—致的; 函数指针的类型和函数的返回值类型也必须是一致的。

2、函数指针的赋值

int func(int x); /* 声明一个函数 */
int (*f) (int x); /* 声明一个函数指针 */
f=func; /* 将func函数的首地址赋给指针f */

赋值时函数func不带括号,也不带参数,由于func代表函数的首地址,因此经过赋值以后,指针f就指向函数func(x)的代码的首地址。

2、【指针函数】

一个函数不仅可以带回一个整型数据的值,字符类型值和实型类型的值,还可以带回指针类型的数据,使其指向某个地址单元。
返回指针的函数,一般定义格式为:

类型标识符 *函数名(参数表)
int *f(x,y);

其中x,y是形式参数,f是函数名,调用后返回一个指向整型数据的地址指针。f(x,y)是函数,其值是指针。
【例】将字符串1(str1)复制到字符串2(str2),并输出字符串2.

#include "stdio.h"
main()
{
    char *ch(char *char *);
    char str1[]="I am glad to meet you!";
    char str2[]="Welcom to study C!";
    printf("%s"ch(str1,str2));
}
char *ch(char *str1,char *str2)
{
    int i;
	char *p;
	p=str2
	if(*str2==NULL) exit(-1);
	do
	{
		*str2=*str1;
		str1++;
		str2++;
	}
 	while(*str1!=NULL);
return(p);
}

通过分析可得:
函数指针是一个指向函数的指针,而指针函数只是说明他是一个返回值为指针的函数, 函数指针可以用来指向一个函数。
区别是:函数指针是指向函数的指针变量,即本质是一个指针变量;指针函数是指带指针的函数,即本质是一个函数。函数返回类型是某一类型的指针,格式为:

类型标识符 *函数名(参数表) ,例如:int *f(x,y)

函数指针是指向函数的指针变量。 因此“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。如前所述,C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上是大体一致的。函数指针有两个用途:调用函数和做函数的参数。

指针函数是一个函数,只不过这个函数的返回值是一个地址值。函数返回值必须用同类型的指针变量来接受,也就是说,指针函数一定有“函数返回值”,而且,在主调函数中,函数返回值必须赋给同类型的指针变量

二:指针运算

从本质上讲,指针也是一种数据,只不过这种数据有点特殊而已。我们通常所见的数据就是各种数值数据文字数据等,而指针所表示的是内存地址数据。既然是数据,那么自然就涉及到了数据的运算。像普通数据一样,指针也可以参与部分运算,包括算术运算、关系运算和赋值运算,而我们最常用的就是指针的算术加减运算。

如果指针的值是某个内存位置的地址值,那么我们就说指针指向这个内存位置。而指针的加减运算,实际上是让指针的指向发生偏转,指向另外的内存位置。通过这种指针的偏转,可以灵活地访问到该指针起始位置附近的内存。如果这种偏移是在某个范围内连续发生的话,则可以通过指针访问到某一连续内存区域的数据。例如,在3.6节中介绍过数组,数组名实际上就是数组数据所在内存区域的首地址,表示数组在内存中的起始位置。可以通过把首地址赋值给指针,然后对该指针进行加减运算,使指针发生偏转指向数组中的其他元素,从而遍历整个数组。例如:

int nArray[3] = { 1, 2, 3 };   
定义一个数组int* pIndex = nArray;          // 将数组的起始地址赋值给指针pIndex
cout<<"指针指向的地址是:"<<pIndex<<endl;  // 输出指针指向的地址cout<<"指针所指向的数据的值是:
cout<<*pIndex<<endl;  // 输出指针所对应的值
对指针进行加运算,使其指向数组中的下一个值
cout<<"指针指向的地址<<pIndex++<<endl;//输出指针加+后的地址         
cout<<"指针+1所指向的数据的值是:"<<*pIndex<<endl;  // 输出指针+1后的值。
这段程序执行后,可以得到这样的输出:

指针指向的地址是:0016FA38

指针所指向的数据的值是:1

指针指向的地址是:0016FA3C

指针所指向的数据的值是:2

从输出结果中可以看到,pIndex指针初始指向的地址是0016FA38,也就是nArray这个数组的首地址。换句话说,也就是pIndex指向的是数组中的第一个数据,所以输出“*pIndex”的值是1。而在对指针进行加1运算后,指针指向的地址变为0016FA3C,它向地址增大的方向偏移了4个字节,指向了数组中的第二个数据,输出“*pIndex”的值自然也就变成了2

这里大家肯定会奇怪,对指针进行的是加1的运算,怎么指针指向的地址却增加了4个单位?这是因为指针的加减运算跟它所指向的数据的真正数据类型相关,指针加1或者减1,会使指针指向的地址增加或者减少一个对应的数据类型的字节数。比如以上代码中的pIndex指针,它可以指向的是int类型的数据,所以它的加1运算就使地址增加了4个字节,也就是一个int类型数的字节数。同样的道理,对于可以指向char类型数据的char类型指针,加1会使指针偏移1个字节;而对于可以指向double类型数据的double类型指针,加2会使指针偏移16(8*2)个字节。指针偏转流程如图。
在这里插入图片描述
除了指针的加减算术运算之外,常用到的还有指针的关系运算。指针的关系运算通常用“==”或“!=”来判断两个相同类型的指针是否相等,也就是判断它们是否指向同一地址上的同一数据,以此作为条件或循环结构中的条件判断语句。例如:

int nArray[3] = { 1, 2, 3 };    // 定义一个数组
int* pIndex = nArray;           // 将数组的起始地址赋值给指针
 pIndexint* pEnd = nArray + 3;      
  ( pIndex != pEnd )         // 计算数组的结束地址并赋值给pEndwhile
  // 在while的条件语句中判断两个指针是否相等,                          
  // 也就是判断当前指针是否已经偏转到结束地址
  {
    cout<<*pIndex<<endl;        // 输出当前指针指向的数据   
    ++pIndex;                  // 对指针进行加1 运算 使其偏移到下一个内存位置,指向数组中的下一个数据
  }

三:动态内存分配: malloc 和 new

1、区别

区别:
new/delete是C++关键字,需要编译器支持。

    malloc/free是库函数,需要头文件支持。

    new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。

    而malloc则需要显式地指出所需内存的尺寸。

    new操作符内存分配成功时,返回的是**对象类型的指针**,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。

    而malloc内存分配成功则是返回**void * ,**需要通过强制类型转换将void*指针转换成我们需要的类型。

    new内存分配失败时,会抛出bac_alloc异常。

    malloc分配内存失败时返回NULL。

    C++允许重载new/delete操作符,

    而malloc不允许重载。

    new操作符从自由存储区(free store)上为对象动态分配内存空间,

    而malloc函数从堆上动态分配内存。

    自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。自由存储区不等于堆,如上所述,布局new就可以不位于堆中。

mallco实现:

/*malloc()负责动态配置内存,大小由size决定,分配成功时返回值为任意类型指针,
指向一段可用内存(虚拟内存)的起始地址。分配失败时为NULL。*/
void * malloc(size_t size)
 
/*free()负责释放动态申请的内存空间,调用free( )后ptr所指向的内存空间被收回,
如果ptr指向未知地方或者指向的空间已被收回,则会发生不可预知的错误,
如果ptr为NULL,free不会有任何作用。*/
void  free(void *ptr)

malloc有两种分配内存的方式,分别由两个系统调用完成:

    brk和mmap(不考虑共享内存)。
    申请小于128k的内存时,使用brk分配内存,brk是将数据段(.data)的最高地址指针_edata往高地址推。
    申请大于128k的内存时,使用mmap分配内存,mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲存储空间。
    这两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。

找空闲存储空间的过程如下:
空闲存储空间以空闲链表的方式组织(地址递增),每个块包含一个长度、一个指向下一块的指针以及一个指向自身存储空间的指针。(因为程序中的某些地方可能不通过malloc调用申请,因此malloc管理的空间不一定连续。)
当有申请请求时,malloc会扫描空闲链表,直到找到一个足够大的块为止(首次适应)(因此每次调用malloc时并不是花费了完全相同的时间)。
如果该块恰好与请求的大小相符,则将其从链表中移走并返回给用户。如果该块太大,则将其分为两部分,尾部的部分分给用户,剩下的部分留在空闲链表中(更改头部信息)。
释放时,首先搜索空闲链表,找到可以插入被释放块的合适位置。如果与被释放块相邻的任一边是一个空闲块,则将这两个块合为一个更大的块,以减少内存碎片。
malloc详细解释

2、malloc的实质

1、malloc函数的实质体现在,它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。调用free函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。如果无法获得符合要求的内存块,malloc函数会返回NULL指针,因此在调用malloc动态申请内存块时,一定要进行返回值的判断。

2、malloc()到底从哪里得来了内存空间
malloc()到底从哪里得到了内存空间?是从堆里面获得空间。也就是说函数返回的指针是指向堆里面的一块内存。操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。

3、free()到底释放了什么
free()释放的是指针指向的内存!释放的是内存,不是指针!指针是一个变量,只有程序结束时才被销毁。释放了内存空间后,原来指向这块空间的指针还是存在!只不过现在指针指向的内容的垃圾,是未定义的,所以说是垃圾。因此,前面我已经说过了,释放内存后把指针指向NULL,防止指针在后面不小心又被解引用了。非常重要啊这一点!

3、new实质

动态申请的内存分配,分配类的内存空间时,同时调用类的构造函数,对内存空间进行初始化,即完成类的初始化工作。new运算符的使用示例:

new int  //开辟一个存放整数的存储空间,返回一个指向该存储空间的地址
new int(100)  //同上,并指定该整数的初值为100
new char[100] //开辟一个存放字符数组(100个元素)的空间,返回首地址
new int[4][5]//开辟一个存放二维数组的空间,返回首元素的地址
float *p=new float(3.14157) //开辟一个存放单精度的空间,并指定该数的初值为3.14157,将返回的该空间的地址赋给指针变量p

注意:用new分配数组空间不能指定初值,若无法正常分配,则new会返回一个空指针NULL或者抛出bad_alloc异常。

delete是撤销动态申请的内存运算符。delete与new通常配对使用,与new的功能相反,可以对多种数据类型形式的内存进行撤销,包括类,撤销类的内存空间时,它要调用其析构函数,完成相应的清理工作,收回相应的内存资源。delete运算符的使用示例:

//注意,指针p存于栈中,p所指向的内存空间却是在堆中。

int *p = new intdelete p;
char *p = new chardelete p;

//注意,new申请数组,delete删除的形式需要加括号“[ ]”,表示对数组空间的操作,总之,申请形式如何,释放的形式就如何。

Obj * p = new Obj[100];               delete [ ]p;

内存分配
new申请的内存也是存于中,所以在不需要使用时,需要delete手动收回。

实现过程
在new一个对象的时候,首先会调用malloc为对象分配内存空间,然后调用对象的构造函数。

delete会调用对象的析构函数,然后调用free回收内存。

new/delete和malloc/free的区别

new从自由存储区上分配内存,malloc从堆上分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配。自由存储区是否能够是堆取决于operator new 的实现细节。自由存储区不仅可以是堆,还可以是静态存储区,这都看operator new在哪里为对象分配内存。
new 可以调用对象的构造函数,对应的 delete 调用相应的析构函数;malloc 仅仅分配内存,free 仅仅回收内存,并不执行构造和析构函数。在new一个对象的时候,首先会调用malloc为对象分配内存空间,然后调用对象的构造函数。delete会调用对象的析构函数,然后调用free回收内存。
使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算;使用malloc则需要显式地指出所需内存的尺寸。
new、delete 返回的是某种数据类型指针;malloc、free 返回的是 void 指针。
new、delete 是操作符;malloc、free 是函数。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值