初学者指针基础

引言


如今,由于技术的改进和价格便宜,内存和处理能力的数量正在稳定增长。

根据摩尔定律:
每24个月,放置在集成电路芯片上的晶体管数量将增加一倍。
请注意,两个参数已更改:

  • 晶体管数
  • 模块尺寸

在RAM(DRAM)的数量上预计有相同的原理。

现在的内存问题已经不那么尖锐了,因为过去10年中每个内存的内存量增加了16倍。

大多数高级编程语言(PL)已经“开箱即用”,隐藏了程序员对内存的工作。 而且,由于这个问题正在睡眠中,因此出现了新的程序员阶层,他们不了解或不想了解内存工作的工作方式。

在本主题中,我们将使用C ++语言的示例来考虑使用内存的要点,因为它是支持直接使用内存并支持OOP的少数命令式语言之一。

IT是做什么用的?


在这里值得一提的是,本文是为刚开始使用C ++或只想了解动态内存的人们而设计的。

在运行时,任何程序都会在DRAM中为其自身保留一块内存。 所有其他DRAM可用空间称为“堆” (英文“ Heap”)。 执行期间为满足程序需要而分配的内存恰好是从堆中分配的,这称为动态内存分配。

整个问题是,如果在不再需要分配内存时不进行清理,则可能会发生所谓的内存泄漏,您的系统(程序)会简单地挂起。 类似于停在路中间的汽车,因为有人忘记按时加油。

你应该已经知道的
大多数现代PL都配备了垃圾收集器,并自行清除内存。
但是,C ++已将自己确立为性能最快的API之一,部分原因是其中所有内存处理都是手动完成的。


新增和删除


内存分配可以是静态的,也可以是动态的。 静态内存分配在程序编译期间称为一次性内存分配,并且静态内存量在运行时不会更改。 一个经典的例子是整数变量或数组的声明。 但是,如果程序员事先不知道容器中需要多少个元素,该怎么办?
当有必要根据程序需要组织内存分配时,建议使用动态内存。
new运算符负责在C ++中分配动态内存,而delete负责清除它。
new运算符将其操作结果返回指向该类新实例的指针。
语法是这样的:

| 数据类型(T1)指针 | * | 指针名称 | = | 输入T1 |;

new运算符之后,您可以使用构造函数,例如,初始化类的字段。
值得注意的是,当程序员失去对其分配的控制权时,也会发生相同的内存泄漏。
重要的是要记住:
如果您忘记了清除“已用”不必要元素的动态内存,那么迟早会有一个关键时刻,那就是根本无处可去。

内存分配及其清理的示例:
int main{ // ,       new int *ptr = new int(); //   cout<<*ptr<<endl; // ,     delete ptr; //  delete     ,         return 0; } 


由于主题非常广泛,因此本文将不讨论所谓的“智能”指针,而是简而言之:“智能指针部分地自动为程序员清除了内存。”

指针


指针负责在C ++中使用动态内存。 这是一个食欲宠坏初学者的话题。

您可以使用*运算符声明一个指针。 默认情况下,它将指向内存的某个随机区域。 为了能够访问所需的内存区域,我们需要将链接(运算符 )传递给所需的变量。

指针本身只是一个存储单元的地址,要访问存储在该单元中的数据,必须将其取消引用。

重要撤退


如果您尝试显示指针而不进行解引用,那么将显示该存储区的地址,而不是其指向的存储区中的值。
要取消引用指针,只需将*运算符放在其名称的前面。


 int main() { // ,          int* pNum= new int(1) ; cout<<*pNum<<endl; //    ,        ,       (   int   ) pNum++; cout<<*pNum<<endl; // ,         return 0; } 



看这样的例子,我想问:“如果您可以立即派生一个变量,为什么这甚至是必要的?”

另一个例子:

我们有一个Programmers类,该类描述了不了解指针的一组程序员的成员

  class Programmers{ public: Programmers(){} Programmers(int iWeight, int iAge){ this->weight = iWeight; this->age = iAge; } int weight; int age; }; int main() { //     Programmers int size = 9; Programmers *prog [size]; //  Programmers Programmers *ptr = nullptr; //     Programmers       //          for (int i =0;i<size;i++) { ptr=new Programmers(i+100,i); prog[i]=ptr; } return 0; } 

这样,我们就可以随意操纵记忆。 这就是为什么在使用内存时,您可以“脚踏实地”。 应该注意的是,使用指针的速度要快得多,因为值本身不会被复制,而是只分配了指向特定地址的链接。

顺便说一句,这种流行的关键字提供了指向当前类对象的指针。 这些指针无处不在。

日常生活中的指针示例:

想象一下在餐厅点菜的情况。 要下订单,您只需指向菜单中的菜肴,您便会做好准备。 同样,其他来餐厅的访客在菜单中指示所需的物品。 因此,菜单中的每一行都是指向菜的烹饪功能的指针,并且该指针是在菜单本身的设计阶段创建的。

功能指针示例
 //      void Chicken(){ cout<<"Wait 5 min...Chicken is cooking"<<endl; } void JustWater(){ cout<<"Take your water"<<endl; } int main() { //    void   void (*ptr)(); ptr = Chicken; ptr(); ptr=JustWater; ptr(); return 0; } 



回到我们的程序员。 假设现在我们需要将类字段带到专用部分,以适应OOP封装的原理,然后我们需要使用getter来获取对这些字段的读取访问权限。 但是,假设我们没有2个字段,而是100个,为此,我们需要为每个字段编写自己的访问器吗?

扰流板
好吧,当然不是,我什至不明白你为什么打开这个扰流板。

为此,我们将创建一个类型为void的“访问器”,并通过引用将参数传递给它。 通过引用传递参数的含义是不复制参数的值,而只传输实际参数的地址。 因此,当改变这样一个自变量的值时,当前自变量的存储单元中的数据也将改变。
这也会影响整体性能,因为按引用传递参数要比按值传递更快。 这更不用说大量的元素了。

例如,内部的getParams方法将更改传入的参数,并且它们将更改它们的值,包括从调用它所在的作用域在内的值。
指针将帮助我们导航数组。 根据数据结构理论,我们知道数组是内存的连续区域,其元素一个接一个地排列。
这意味着,如果将指针的值更改为元素在数组中占据的字节数,则可以到达每个元素,直到指针超出数组的边界为止。
创建另一个指针,该指针指向程序员数组的第一个元素。

 class Programmers{ public: Programmers(){} Programmers(int iWeight, int iAge){ this->weight = iWeight; this->age = iAge; } //    ,   main     void getParams(int &w, int &a){ w=weight; a=age; } private: int weight; int age; }; int main() { int size = 9; Programmers *prog [size]; Programmers *ptr=nullptr; for (int i =0;i<size;i++) { ptr=new Programmers(i+100,i); prog[i]=ptr; } int w,a; int count = 9; //    //        Programmers **iter = prog; for (int i=0;i<count;i++) { ptr = *iter++; ptr->getParams(w,a); if(*(iter-1) != nullptr){ delete *(iter-1); ptr = nullptr; } cout<<w<<"\t"<<a<<endl; } return 0; } 



在此示例中,我想向您传达一个事实,即当您更改指针地址的值时,您可以访问内存的另一个区域。

数据结构,例如列表,向量等。 基于指针的,因此称为动态数据结构。 并且要遍历它们,使用迭代器更为正确。 迭代器是指向数据结构元素的指针,并提供对容器元素的访问。

总结


了解了指针的主题之后,使用内存就成为编程中令人愉快的一部分,并且总体上似乎对机器如何使用内存以及如何对其进行管理有了详细的了解。 从某种意义上说,“处理内存”这一概念背后有一种哲学。 触手可及的是,即使是很小的电容器,也可以改变板上的电荷。

Source: https://habr.com/ru/post/zh-CN456318/


All Articles