当前位置: 首页 > article >正文

c++中的指针相关

new

new用来实现运行时分配内存(不同于int a = 123会在编译时就分配有名称的内存),new会返回指针类型。

使用new有什么用?1、灵活,由于程序不会在编译时进行内存分配,节省内存。

缺点:1、 增加心智负担,要谨防内存泄漏,注意回收内存(只能指针有利于环节内存泄露)。2、new和delete可能导致内存空间不连续,造成内存碎片

内存耗尽

如何没有足够内存满足new请求,就会报异常

delete

用于使用完后释放内存, delete后面跟指向内存的地址

delete注意事项

1、 不要使用delete释放不是new分配的内存

2、不要释放同一个内存两次

c++中的存储分类

c++有三种管理数据内存方式如下

自动存储

函数内的变量为自动存储,函数执行完毕内部定义的变量会自动销毁。

静态存储

静态存储是整个程序执行期间都存在的存储方式。使用关键字static

动态存储

使用new的变量都是动态存储。这种变量的生命周期受程序员控制。

引用变量

引用变量是为了实现按引用传递参数,超越C语言。

引用变量优点类似于指针,我们修改引用变量原数据也会被改变,但是和指针有一些区别

    int refer = 2;
    int& refer_ref = refer;
    cout << "refer:" << refer << refer_ref << endl; // 2  2
    refer_ref = 3;
    cout << "refer:" << refer << endl; // 3

引用更像const 指针,必须在创建初始化,且和某个变量关联后无法修改。

int num = 3;
int& num_ref = num; // 等同于 int* const num_ref = &num;

区别如下

1、语法不同一个是&一个是*

2、声明引用变量时必须初始化,而指针可以先声明再赋值。

智能指针

在 C++ 中,内存管理是一个重要且容易出错的部分。当通过new操作符动态分配内存后,必须使用delete操作符来释放内存,否则会导致内存泄漏。智能指针是一种类模板,用于自动管理动态分配的内存,帮助程序员避免忘记释放内存或者错误释放内存(如多次释放同一块内存)等问题。

std::unique_ptr

独占所有权语义std::unique_ptr是一种独占式智能指针,它确保同一时刻只有一个unique_ptr对象拥有一块动态分配的内存。当这个unique_ptr对象被销毁时(例如离开作用域),它所管理的内存会自动被释放。

   #include <memory>
   #include <iostream>

   class MyClass {
   public:
       MyClass() { std::cout << "MyClass constructor" << std::endl; }
       ~MyClass() { std::cout << "MyClass destructor" << std::endl; }
   };

   int main() {
       std::unique_ptr<MyClass> ptr(new MyClass);
       // 不需要手动释放内存,ptr离开作用域时会自动调用MyClass的析构函数释放内存
       return 0;
   }

转移所有权std::unique_ptr的所有权可以通过std::move函数进行转移。例如:

   std::unique_ptr<MyClass> ptr1(new MyClass);
   std::unique_ptr<MyClass> ptr2 = std::move(ptr1);
   // 此时ptr1为空,ptr2拥有原来ptr1指向的对象

std::shared_ptr

std::shared_ptr实现了共享式所有权。多个shared_ptr对象可以同时拥有同一块动态分配的内存,并且通过引用计数来记录有多少个shared_ptr对象在引用这块内存。当引用计数为 0 时,即没有shared_ptr对象引用这块内存时,才会释放内存。

   std::shared_ptr<MyClass> ptr1(new MyClass);
   std::shared_ptr<MyClass> ptr2 = ptr1;
   // 此时ptr1和ptr2都指向同一个MyClass对象,引用计数为2

shared_ptr的问题

std::shared_ptr可能会出现循环引用的问题。例如,有两个类AB,它们相互包含shared_ptr成员,当它们的对象通过shared_ptr相互引用时,会导致引用计数永远不为 0,从而无法释放内存。为了解决这个问题,可以使用std::weak_ptrstd::weak_ptr

std::weak_ptr

std::weak_ptr用于解决std::shared_ptr的循环引用问题。它不增加对象的引用计数,只是作为一个观察者。当需要访问weak_ptr所指向的对象时,可以通过lock函数来获取一个shared_ptr对象,如果对象已经被销毁,lock函数会返回一个空的shared_ptr

   class A {
   public:
       std::shared_ptr<B> ptrB;
   };
   class B {
   public:
       std::weak_ptr<A> ptrA;
   };   
std::shared_ptr<A> a(new A);
       std::shared_ptr<B> b(new B);
       a->ptrB = b;
       b->ptrA = a;
       // 在这里,由于B中使用的是weak_ptr,不会导致循环引用,对象最终可以正常销毁

标准库

模板类vector

vector相当于是动态数组,动态长度。

使用vector必须先引入。

#include<vector>
vector<int> vi;

泛型

在 C++ 中,泛型是一种编程范式,它允许编写能够处理多种数据类型的代码,而不是为每种特定的数据类型编写重复的代码。泛型编程的核心思想是将算法与数据类型分离,使得算法可以应用于不同类型的数据,从而提高代码的复用性和可维护性。

函数模版

函数模板是泛型编程的一种形式,它允许定义一个通用的函数框架,这个函数可以用于多种不同类型的数据。函数模板的定义以template关键字开头,后面跟着模板参数列表,通常是一个或多个类型参数。例如,下面是一个简单的函数模板,用于交换两个变量的值:

   template<typename T>
   void swap(T& a, T& b) {
       T temp = a;
       a = b;
       b = temp;
   }
   int main() {
       int a = 10, b = 20;
       swap(a, b);
       double c = 1.2, d = 3.4;
       swap(c, d);
       return 0;
   }

当调用函数模板时,编译器会根据实际传入的参数类型自动推导出模板参数T的类型,并生成相应的函数实例。

模版参数的多种形式

除了类型参数(如T),函数模板还可以有非类型参数,如整数、指针等。例如,下面是一个函数模板,它接受一个整数参数N,用于打印一个指定大小的数组:

   template<typename T, int N>
   void printArray(T (&arr)[N]) {
       for (int i = 0; i < N; ++i) {
           std::cout << arr[i] << " ";
       }
       std::cout << std::endl;
   }

类模板

类模板允许创建通用的类定义,其中类的成员类型和成员函数的参数类型可以是模板参数。例如,下面是一个简单的类模板,用于表示一个动态大小的栈:

   template<typename T, int MAX_SIZE = 100>
   class Stack {
   public:
       Stack() : top(-1) {}
       void push(T value);
       T pop();
   private:
       T data[MAX_SIZE];
       int top;
   };

成员函数的定义

类模板的成员函数也需要是模板函数,其定义方式有两种。一种是在类模板内部定义成员函数,另一种是在类模板外部定义。在外部定义时,需要在函数定义前加上模板参数列表,以表明这是一个类模板的成员函数。例如,上面Stack类模板中pushpop函数的定义如下(使用类模板时,需要指定模板参数的具体类型和值(如果有非类型参数)):

   template<typename T, int MAX_SIZE>
   void Stack<T, MAX_SIZE>::push(T value) {
       if (top < MAX_SIZE - 1) {
           data[++top] = value;
       }
   }
   template<typename T, int MAX_SIZE>
   T Stack<T, MAX_SIZE>::pop() {
       if (top >= 0) {
           return data[top--];
       }
       throw std::out_of_range("Stack is empty");
   }
   int main() {
       Stack<int> intStack;
       intStack.push(10);
       intStack.push(20);
       std::cout << intStack.pop() << std::endl;
       Stack<double, 200> doubleStack;
       doubleStack.push(1.2);
       doubleStack.push(3.4);
       std::cout << doubleStack.pop() << std::endl;
       return 0;
   }

右值引用

c++11新增,使用&&表示。那什么是右值引用呢?右值是指临时对象,或者是不具有持久存储的对象,例如字面量、表达式返回的临时值等。例如:

int&& rvalue_ref = 30;  // 30是右值,rvalue_ref是右值引用

那右值引用解决了什么问题呢?主要是避免不必要的复制开销

  • 例如,当一个函数返回一个大型对象(如包含大量数据成员的自定义类)时,按照旧的语义,会创建一个临时对象用于返回值,然后通过复制构造函数将这个临时对象复制到接收该返回值的变量中。如果这个对象的数据量很大,或者对象内部包含动态分配的资源(如堆内存),这种复制操作会带来很高的性能成本。
  • 右值引用提供了移动语义,使得可以将资源从一个即将销毁的临时对象(右值)转移到另一个对象,而不是进行复制。

移动语义

移动语义就是让编译器知道什么时候需要复制什么时候不需要。

   class String {
   public:
       char* data;
       String(const char* str) {
           if (str) {
               size_t len = strlen(str);
               data = new char[len + 1];
               strcpy(data, str);
           } else {
               data = nullptr;
           }
       }
       // 移动构造函数
       String(String&& other) noexcept {
           data = other.data;
           other.data = nullptr;
       }
       ~String() {
           delete[] data;
       }
   };
   String getString() {
       String local("Hello");
       return local;
   }
   int main() {
       String result = getString();
       return 0;
   }

getString函数中返回local对象时,因为local是一个局部对象(右值),当String类有移动构造函数时,编译器会优先使用移动构造函数将local对象的资源(data指针所指向的字符数组)移动到result对象中,而不是进行复制,从而避免了复制local对象中的字符串数据,从而提高了性能。

完美转发

右值引用还是实现完美转发的关键。在函数模板中,完美转发可以保持参数的原始值类别(左值或右值)不变地传递给其他函数。这在编写泛型代码时非常重要,例如在实现工厂模式或者代理模式等设计模式的泛型版本时。

   void processValue(String&& value) {
       // 对value进行操作,比如打印它所管理的字符串
       std::cout << value.data << std::endl;
   }
   template<typename T>
   void forwardFunction(T&& arg) {
       processValue(std::forward<T>(arg));
   }
   int main() {
       String temp("Test");
       forwardFunction(temp);  // 传递左值
       forwardFunction(String("Another Test"));  // 传递右值
       return 0;
   }

forwardFunction函数模板中,参数arg是一个万能引用(根据传入的是左值还是右值可以是左值引用或右值引用)。std::forward<T>(arg)会根据T的类型(通过模板参数推导得到),以正确的方式转发arg。这样就可以确保在调用processValue函数时,参数的原始值类别得以保持,并且在合适的情况下(如传递右值时)利用移动语义,避免不必要的拷贝。


http://www.kler.cn/a/372896.html

相关文章:

  • 编程相关学习点——代码内容及结构
  • 【解决方案】微信小程序如何使用 ProtoBuf 进行 WebSocket 通信
  • 社区交流系统设计与实现
  • HTB:BoardLight[WriteUP]
  • MySQL 分库分表
  • kubernetes——part2-3 使用RKE构建企业生产级Kubernetes集群
  • [Linux关键词]unmask,mv,dev/pts,stdin stdout stderr,echo
  • 使用原生HTML和css制作一个箭头步骤条
  • 【Nas】X-DOC:Mac mini Docker部署小雅Alist
  • Vue v-on
  • Android 在github网站下载项目:各种很慢怎么办?比如gradle下载慢;访问github慢;依赖下载慢
  • c++中的结构体
  • 深度了解flink(七) JobManager(1) 组件启动流程分析
  • 【HarmonyOS】鸿蒙应用低功耗蓝牙BLE的使用心得 (一)
  • 四款国内外远程桌面软件横测:ToDesk、向日葵、TeamViewer、AnyDesk
  • go-logger v0.27.0 - 并发性能为官方库 10 倍
  • uv: 一个统一的Python包管理工具
  • 游戏引擎中的颜色科学
  • 使用docx4j+docx4j-ImportXHTML实现将html转成word
  • PHP合成图片,生成海报图,poster-editor使用说明
  • 华为云Stack名词解释
  • 嵌入式硬件电子电路设计(一)开关电源Buck电路
  • es安装拼音分词后Kibana出现内存错误
  • HTML入门教程8:HTML格式化
  • 数据采集-Kepware OPCUA 服务器实现
  • 基于单片机的直流电机控制系统(论文+源码)