尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

11. 类和动态内存分配

11. 类和动态内存分配 类和动态内存分配动态内存和类在类中使用 new 和 delete 来动态管理内存可以让程序在运行时而非编译时决定内存分配。在类构造函数中使用 new需要额外执行一系列步骤扩展析构函数、编写额外的类方法来帮助正确完成初始化和赋值。否则会导致严重问题。#include cstring #include strngbad.h using std::cout; ​ // 初始化静态成员 int StringBad::num_strings 0; ​ // 从 C 风格字符串构造 StringBad StringBad::StringBad(const char * s) { len std::strlen(s); str new char[len 1]; // 分配内存 std::strcpy(str, s); // 复制字符串 num_strings; // 对象计数 1 cout num_strings : \ str \ object created\n; } ​ // 默认构造函数 StringBad::StringBad() { len 4; str new char[4]; std::strcpy(str, C); num_strings; cout num_strings : \ str \ default object created\n; } ​ // 析构函数 StringBad::~StringBad() { cout \ str \ object deleted, ; --num_strings; cout num_strings left\n; delete [] str; // 释放动态分配的内存 } ​ std::ostream operator(std::ostream os, const StringBad st) { os st.str; return os; }使用赋值运算符的时候编译器会使用默认复制构造函数会产生浅拷贝导致程序调用两次析构函数产生未定义行为。特殊成员函数何时自动生成默认行为默认构造函数没有定义任何构造函数不执行任何操作默认析构函数没有定义析构函数不执行任何操作复制构造函数程序使用对象的方式需要时逐成员复制浅拷贝赋值运算符程序使用对象的方式需要时逐成员复制浅拷贝地址运算符程序使用对象的方式需要时返回this指针C11 还新增了移动构造函数和移动赋值运算符第18章用于支持移动语义。默认构造函数如果没有提供任何构造函数编译器会生成一个隐式默认构造函数Klunk::Klunk() { } // 什么都不做但是如果定义了任意一个构造函数编辑器就不会生成默认构造函数了。默认构造函数的定义方式// 方式一无参构造函数 Klunk() { klunk_ct 0; } ​ // 方式二所有参数都有默认值 Klunk(int n 0) { klunk_ct n; }复制构造函数Class_name(const Class_name );复制构造函数的调用时机。显式初始化新对象为现有对象函数按值传递对象函数按值返回对象编译器生成临时对象。按值传递会调用复制构造函数因此对于大型对象应优先使用按引用传递以节省时间和内存。默认构造函数默认浅拷贝。在构造函数中使用new规则析构函数必须使用deletenew和delete匹配。new—deletenew[]—delete[]必须自定义复制构造函数深拷贝。必须自定义赋值运算符。必须检测自我赋值对象的返回方式返回指向对象的引用返回指向对象本身返回const对象返回指向const对象引用。可以提高效率避免调用赋值构造函数// 版本1按值返回调用复制构造函数效率较低 Vector Max(const Vector v1, const Vector v2) { if (v1.magval() v2.magval()) return v1; else return v2; } ​ // 版本2返回 const 引用效率更高 const Vector Max(const Vector v1, const Vector v2) { if (v1.magval() v2.magval()) return v1; // 返回引用指向调用者已存在的对象 else return v2; }返回非const对象的引用两种场景场景一赋值运算符考虑到调用者可能对其进行修改如(s2 s1).some_method()。选择*this。String String::operator(const String st) { if (this st) return *this; delete [] str; // ... 复制操作 ... return *this; // 返回非 const 引用 }场景二operator 与 cout目的支持链式输出如 cout s1 is coming!;。ostream operator(ostream os, const String st) { os st.str; return os; // 必须返回 ostream }由于ostream没有公有的复制构造函数返回引用支持链式调用。为什么不能使用const引用ostream内部对象都是非const对象一个 const 对象无法绑定到非 const 引用参数。输出流对象在输出过程中会改变内部状态比如设置错误位、刷新缓冲区等使用对象的指针使用new初始化对象的语法// 使用复制构造函数 String * favorite new String(sayings[choice]); ​ // 使用默认构造函数 String * gleep new String; ​ // 使用带参构造函数 String * glop new String(my my my);对定位 new 创建的对象使用 delete 是错误的因为 delete 只应用于常规 new 分配的地址必须显式调用析构函数。常规new运算符和定位new运算符常规 new“分配内存 调用构造函数”。如果内存不够它会自己去堆上找。定位 newPlacement new“只调用构造函数”。它不管内存从哪来你已经提供好了地址它就在那个地址上构造对象// 常规 new String* p1 new String(Hello); ​ // 定位 new多了一个括号参数传入地址 #include new // 必须包含头文件 char buffer[100]; // 你准备好的“空地”栈上 String* p2 new (buffer) String(World); // 在 buffer 这块内存上构造 String复习重载 可以使用友元函数或者通过公有函数暴露接口写一个全局函数推荐返回值使用ostream 非const引用转换函数一个参数的构造函数推荐使用explicit禁止隐式类型转换。赋值运算符必须检查自我复制。队列模拟#ifndef QUEUE_H_ #define QUEUE_H_ ​ // 客户类模拟队列中存储的数据类型 class Customer { private: long arrive; // 到达时间分钟 int processtime; // 服务所需时间1~3分钟 public: Customer() : arrive(0), processtime(0) {} void set(long when); long when() const { return arrive; } int ptime() const { return processtime; } }; ​ typedef Customer Item; // 队列中的项目类型 ​ // 队列类 class Queue { private: // 嵌套结构链表节点作用域限定在类内部 struct Node { Item item; struct Node * next; }; ​ enum { Q_SIZE 10 }; // 默认队列容量 ​ // 数据成员 Node * front; // 指向队首 Node * rear; // 指向队尾 int items; // 当前元素个数 const int qsize; // 最大容量const成员必须用初始化列表 ​ // 禁止复制私有化防止浅拷贝导致的链表共享问题 Queue(const Queue q) : qsize(0) { } Queue operator(const Queue q) { return *this; } ​ public: // 构造函数指定容量默认10 Queue(int qs Q_SIZE); // 析构函数释放所有节点 ~Queue(); // 状态检查 bool isempty() const; bool isfull() const; int queuecount() const; ​ // 核心操作 bool enqueue(const Item item); // 入队队尾添加 bool dequeue(Item item); // 出队队首移除 }; ​ #endif#include queue.h #include cstdlib // 用于 rand() ​ // ---------- Customer 类方法 ---------- void Customer::set(long when) { processtime std::rand() % 3 1; // 随机生成 1~3 分钟的服务时间 arrive when; } ​ // ---------- Queue 类方法 ---------- ​ // 构造函数初始化空队列 Queue::Queue(int qs) : qsize(qs), front(nullptr), rear(nullptr), items(0) { // 函数体为空所有初始化已在初始化列表中完成 } ​ // 析构函数删除所有剩余节点 Queue::~Queue() { Node * temp; while (front ! nullptr) { temp front; // 保存当前队首 front front-next; // 移动队首指针 delete temp; // 删除旧节点 } } ​ // 检查队列是否为空 bool Queue::isempty() const { return items 0; } ​ // 检查队列是否已满 bool Queue::isfull() const { return items qsize; } ​ // 返回当前队列中的元素个数 int Queue::queuecount() const { return items; } ​ // 入队在队尾添加元素 bool Queue::enqueue(const Item item) { if (isfull()) return false; ​ // 创建新节点 Node * add new Node; add-item item; // 复制数据 add-next nullptr; // 新节点将成为队尾next 指向空 ​ items; // 计数加1 ​ // 链接到队列中 if (front nullptr) // 如果队列为空 front add; else rear-next add; // 否则接到当前队尾后面 ​ rear add; // 更新队尾指针 return true; } ​ // 出队从队首移除元素数据存入引用参数 bool Queue::dequeue(Item item) { if (front nullptr) return false; ​ item front-item; // 取出队首数据 items--; // 计数减1 ​ Node * temp front; // 保存旧队首地址 front front-next; // 移动队首指针 delete temp; // 释放旧节点 ​ if (items 0) // 如果队列已空 rear nullptr; ​ return true; }
返回列表