【C++】指针及动态管理内存相关

一:指针
(1)存储数据两种方式
计算机在储存数据时,需要跟踪数据的储存地址,所储存的值的类型及大小。在介绍指针操作以前,通过定义简单变量来实现这一问题。比如语句:int x = 1;实际上是程序分配足够放置int类型变量的内存(比如4个字节),将这部分内存标记为x,同时将数值1放入其中。在这种方式下,数据的值是指定量,存放的地址则是派生量;而利用指针操作存储数据,将地址视为指定量,数据的值则是派生量。
(2)获取地址
地址在内存中以8位16进制数表示,使用常规变量时若想取得变量地址只需对变量使用取地址操作符(&)即可,且需要注意的是地址为所分配内存单元的起始地址,这一点可以通过程序验证,比如如下代码:

 int x = 1;
 float y = 1.25;
 double z = 1.50;
 cout<<"The address of x is "<<&x<<endl;
 cout<<"The address of y is "<<&y<<endl;
 cout<<"The address of z is "<<&z<<endl;

其执行结果为:
在这里插入图片描述
从输出结果可以看到:
y的地址-z的地址=0x6ffe18-0x6ffe10=8=double(变量z的类型)所占字节数
x的地址-y的地址=0x6ffe1c-0x6ffe18=4=float(变量y的类型)所占字节数
这里也侧面反应了取地址操作符(&)所取的地址是内存的初始地址的说法:正因为地址为首地址,所以临近的下一个变量的地址也是上一个变量的结束地址,两个地址之间的差值即为上一个变量所占字节数
(3)指针的解除应用
而* 被称为接触应用操作符,通过这个操作符可以获得指针所指向内存中的数据,如果说p_vaule是一个指向int类型变量的指针,那么* p_vaule和int变量等效,也就是说可以通过对* p_vaule操作改变变量的值。
(4)指针的声明和初始化
虽说上面的范例输出中显示的内存地址都用8位数表示,但这并不意味着声明指针时不需要考虑变量的类型,比如说:

int * p;

这句声明语句可以有两种理解方式,第一种理解为int* p,强调的是int*是一种类型,突出的是p是指向int类型数据的指针;第二种理解为int *p,突出的是 *p是int类型的变量。
实际上, * 操作符两边的空格对声明指针没有影响,但是这表示声明指针时要考虑欲操作变量的类型,同时要注意声明时每一个变量都要使用一个 *,类似于int *p1,p2;这种声明是不会建立两个指针的,这条语句会建立一个指针p1和一个常规int型变量p2。
声明了指针以后对指针进行初始化,也可以在声明指针时同时初始化,比如:

int x = 1;
int * p = &x;

需要注意的是,当创建指针以后,计算机会分配用来存放指针(地址)的内存,但是并不会创建指针指向数据的内存,忽略这一步会带来错误,即是说类似于:int * p = 233;这种声明是不可取的!
(5)指针和数组及指针算术
①既然可以利用指针来指向int,char,double等类型的变量,那么和指针同为复合类型的数组能不能由指针指向呢?实际上是可以的,甚至说基于指针算术和C++内部处理数组的方式,指针和数组基本是等价的!比如下面这段代码:

 int numbers[3] = {1,2,3};
 int * pointer = numbers;
 cout<<"pointer is "<<pointer<<", *pointer is "<<*pointer<<", number[0] is "<<numbers[0]<<endl;
 pointer += 1;
 cout<<"after change"<<endl;
 cout<<"pointer is "<<pointer<<", *pointer is "<<*pointer<<", number[1] is "<<numbers[1]<<endl;
 pointer += 1;
 cout<<"after change"<<endl;
 cout<<"pointer is "<<pointer<<", *pointer is "<<*pointer<<", number[2] is "<<numbers[2]<<endl; 

代码的执行结果为:
在这里插入图片描述
来看一下这段代码反应的问题,首先不同于int,char等类型变量的指针初始化操作,在用指针指向数组时直接将数组名赋给了指针,而能够直接赋给指针的只有地址,那么也就是说:C++把函数名解释为地址。结合之前介绍的地址是内存的首地址和上面范例代码的输出结果,不难得出数组名被解释为数组第一个元素的地址。也就是说:
number = &number[0];
同时,在上面的范例中涉及到了指针算术的概念。在将数组名赋给指针后,将指针+1,发现指针所指向的地址增加了4个字节——所指向类型(int)所占内存大小,即指针指向了数组的第二个元素。
小结:数组名被解释为数组第一个元素的内存;可以将指针和整数x相加,结果为原来的指针值加上x与指针所指向类型变量所占字节数的乘积,此时指针指向原来指向数组元素后面第x个元素;两个指针相减得到两个元素间隔。
②数组和指针的关系可以扩展到C风格字符串,在之前的博客【C++】字符串详解中,提到过两种字符串的区别和联系,而C风格字符串是以空字符’\0’结尾的字符数组,也就是说字符串的名称也是第一个字符数组元素的地址。那么回想一下字符串的输出,比如下面这段代码:

 char numbers[4] = "123";  //预留一个元素给隐式的空字符 
 cout<<numbers;

为什么同样是数组,char型数组不需要像int,float型的数组一样遍历输出元素而是直接可以用数组名输出呢?这里涉及到了cout函数的内容,将一个char字符的地址传递给cout函数时,cout会自动打印到非char元素为止,比如说下面这段代码:

 char numbers[4] = "123";  //预留一个元素给隐式的空字符 
 char * p_num = numbers;   //将数组第一个元素地址赋给指针 
 cout<<p_num<<endl;        //将第一个元素地址传递给cout
 p_num += 1;            //指针+1,成为第二个元素地址
 cout<<p_num;             //将新的指针传递给cout 

输出结果为:
在这里插入图片描述
但是这种情况下如何输出char型指针的值呢?毕竟如果输出地址会自动打印到字符串末尾。这种情况下使用(int*)来输出地址即可。
实际上,也可以用像

char * number = "1234"

这样的指针声明语句指向字符串,一般来说编译器在内存中预留部分空间储存源代码中所有被双引号括起的字符串并将被储存的数据和其地址关联起来,而这也是很多提示性语句的储存模式,甚至可以使用:

cout<<(int*)"1234"; 

这样的代码来查看这些字符串的储存位置,将这些被双引号括起字符串的地址赋给指针,就能像上面操作数组一样操作这些字符串了。

二:动态管理内存
经过上面的介绍,对指针应该有了一个初步的了解。但是就目前的程度来说指针的强大之处并没有体现出来。指针作为功能最强大的C++工具之一,更主要的功能是能够动态的分配内存–在运行阶段分配未命名的变量储存值。传统的过程性编程在编译阶段进行决策,而oop编程在运行阶段进行决策,而这为编程带来了很大的灵活性。比如说想要声明一个数组,那么传统的方法需要指定数组的大小,并在编译阶段为数组分配对应大小的内存。无论这些内存是不是在程序运行时被使用了,这些内存都已被分配而不能再被使用,这很可能造成内存的浪费。比如说储存10个人的名字。一个人的名字,一般来说20个字符就足够放置了,但是如果待处理数据中有一个人的名字很长(比如名字有75个字的毕加索),那么为了程序的正确性,必须定义10个长为75的字符数组,这样就会造成很大的内存浪费。
通过使用指针动态管理内存,就可以在运行阶段为程序分配内存,比如这次使用5个元素,下次使用10个元素。使用关键词new请求正确数量的内存并用指针跟踪新分配内存的位置,可以极大减少内存的浪费。
①使用new分配内存和delete释放内存
在之前,将指针初始化为编译时分配的,有名字的内存的地址。为了达到动态分配内存的目的,需要在运行阶段为指定类型分配未命名内存并用指针访问这个值。为一个数据对象分配指定内存的通用格式如下:
typename * pointername = new typename;
当使用完new分配的内存以后,可以使用delete将其内存返还给内存池供其他部分使用,delete后接指向内存指针,这样会释放new分配内存而不会删除指针,通用格式为:
delete pointername;
这样就实现了动态的内存管理,让C++管理内存数据包更高效。下面以一段代码为例:

 int * p_num = new int;  //为int分配内存 
 *p_num = 1;             //将*p_num的值设为1
 cout<<"*p_num is "<<*p_num<<" at "<<p_num; //输出分配内存的地址和值 
 delete p_num;           //将动态分配的内存释放

这段代码的输出结果为:
在这里插入图片描述
②建立和使用动态数组
在对小型数据对象来说,使用简单对象比使用new和指针更简单,而管理大型数据(数组,字符串和结构)的时候使用new更好。如果通过声明来创建数组,那么在编译时为其分配内存空间,无论其是否使用数组,这样的方式称为静态联编。而使用new,如果需要数组则创建数组,并且可以在程序运行时选择数组的长度,这种方式则称为动态联编。
在C++中,确定数组的元素类型和元素个数即可,释放时需要加上【】显示释放的是动态数组,以下面的一段代码为例:

 int * number = new int[3];
 delete [] number;

需要注意的是,不要用delete释放不是由new分配的内存,也不要delete释放同一个内存块两遍,delete时指针要放在数组第一个元素地址上。
声明好动态数组以后,就可以对其中的元素进行赋值进而使用数组。方法也很简单,直接把指针名当作数组名就行了。如果没有学过指针看到这里可能大吃一惊,但是经过本篇博客之前的介绍,大家了解了指针和数组的关系应该就不会过于惊讶了。需要注意的一点是,动态分配数组不是传统的声明数组,不要把这当作传统的数组在声明的时候对其进行初始化!类似于:

int * number = new int[3];
 number = {1,2,3};

以及:

int * number = new int[3] = {1,2,3};

这样的写法都是错误的!但是同时,传统的数组名和指针还是有一些不同的。数组名总是数组第一个元素的地址,是不能变化的。而指针是可以通过指针算术改变指向的元素的。比如说,刚刚声明时pointer[0]就是动态数组的第一个元素,那么pointer += 1后pointer[0]则变成动态数组的第二个元素。
接下来用一段代码演示如何声明并使用动态数组:

 int * pnum = new int[3];  //声明一个大小为3的动态int数组 
 pnum[0] = 1;              //将指针视为数组名为其中元素赋值 
 pnum[1] = 2;
 pnum[2] = 3;
 cout<<"pnum[0] is "<<pnum[0]<<endl; 
 cout<<"pnum[1] is "<<pnum[1]<<endl;
 pnum += 1;                //改变指针指向数组元素,此时指针指向数组第二个元素 
 cout<<"after pnum += 1"<<endl;
 cout<<"pnum[0] is "<<pnum[0]<<endl;
 cout<<"pnum[1] is "<<pnum[1]<<endl;
 pnum -= 1;                //释放数组内存之前将指针移到数组第一个元素地址,否则不能完全释放 
 delete [] pnum;           //释放数组内存 

这段代码的输出结果为:
在这里插入图片描述
从输出结果也可以看到移动指针会给使用数组元素造成影响。
③建立和使用动态结构
在运行时建立数组优于在编译时建立数组,对结构来说也是这样。用new创建结构分为创建结构和访问成员两步组成。创建结构并不特殊,通用格式为:
structName * pointer = new structName;
但是在访问结构成员时,因为此时只知道地址不知道结构体实例名称,不能再使用句点操作符(.)来访问结构成员,C++此时使用箭头成员操作符(->)来访问结构中的成员,实际上也可以用(*pointer)来表示结构体实例,这样就可以用句点操作符(.)来访问结构成员。接下来以一段代码显示建立和使用动态结构:

#include <iostream>
using namespace std;
struct student{
 char name[10];
 int age;
 int score;
}; 
int main()
{
 student * pstu = new student;       //创建一个动态结构 
 cout<<"please input the name of student:";
 cin.getline(pstu->name,10);         //使用cin.get()读取名字 
 cout<<"please input the age of student:";
 cin>>pstu->age;                     //利用->操作符访问结构成员 
 cout<<"please input the score of student:";
 cin>>(*pstu).score;                 //利用(*pointer)表示结构实例,用句点操作符访问结构成员 
 cout<<"Name:"<<pstu->name<<endl;
 cout<<"Age:"<<pstu->age<<endl;
 cout<<"Score:"<<(*pstu).score;
 delete pstu;                        //释放内存 
 return 0; 
 }

这个程序的测试结果为:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值