C++ 之动态存储和类
特殊的成员函数
C++自动提供了下面这些成员函数
- 默认构造函数,如果没有定义构造函数
- 默认析构函数,如果没有定义
- 复制构造函数,如果没有定义
- 赋值运算符,如果没有定义
- 地址运算符,如果没有定义
C++ 11还提供了两个特殊的成员函数:移动构造函数和移动赋值运算符
copy构造函数
何时调用:新建一个对象并将其初始化为同类现有对象时, 复制构造函数都将被调用。
下面4种声明都会调用复制构造函数。
StringBad ditto(motto); StringBad metoo = motto; String also = StringBad(motto); StringBad * pStringBad = new StringBad(motto);
中间的2种声明可能会使用复制构造函数直接创建metoo和also, 也可能使用复制构造函数生成一个临时对象, 然后将零时对象的内容赋给metoo和also, 这取决于具体的实现。 最后一种声明使用motto初始化一个匿名对象,并将新对象的地址赋给pstring指针。
按值传递意味着创建原始变量的一个副本。编译器生成临时对象时, 也将使用复制构造函数。
默认的复制函数的功能
默认的复制构造函数将逐个复制非静态成员(成员复制也称为浅复制),复制的是成员的值。
StringBad sailor = sports; 等效下面的代码: StringBad sailor; sailor.str = sports.str; sailor.len = sports.len;
浅复制会带来 两次释放指针的错误。
解决这一问题使用深度复制(deep copy).
StringBad::StringBad(const StringBad & st) { num_strings ++ ; len = st.len; str = new char[len+1]; std::strcpy(str,st.str); }
与复制构造函数类似,复制运算符的隐式实现也对成员进行逐个复制。 如果成员本身就是类对象,则程序将使用为这个;类定义的赋值运算符来复制该成员, 但静态数据成员不受影响。
Class_name &Class_name::operator=(const Class_name &);
编写赋值运算符:
StringBad & String::operator=(const StringBad & st) { if(this == &) { return *this; } delete [] str; len = st.len; str = new char[len+1]; std:: strcpy(str,st.str); return *this; }
C++11的空指针
str = nullstr;
头文件
string1.h
#ifndef STING1_H_ #define STING1_H_ #include <iostream> using std::istream; using std::ostream; class String { private: char *str; int len; static int num_strings; static const int CINLIM = 80; // 字符长度限制 public: // 构造函数 String(const char *s); String(); String(const String &s); ~String(); // 成员方法 int length() const { return len; }; // 重载运算符成员方法 String &operator=(const String &); String &operator=(const char *); char &operator[](int i); const char &operator[](int i) const; // 重载运算符友元函数 friend bool operator>(const String &str1, const String &str2); friend bool operator<(const String &str1, const String &str2); friend bool operator==(const String &str1, const String &str2); friend ostream & operator<<(ostream & os, const String &str); friend istream & operator>>(istream & is, String &str); }; #endif
string1.cpp源文件
#include "string1.h" #include <cstring> using std::cout; using std::cin; using std::endl; int String::num_strings = 0; int String::HowMany() { return num_strings; } String::String(const char* s) { cout << "const char* s构造" << endl; len = std::strlen(s); str = new char[len + 1]; std::strcpy(str,s); num_strings ++ ; } String::String() { cout << "无参构造" << endl; len = 4; str = new char[1]; str[0] = '\0'; num_strings ++; } String::String(const String & st) { cout << "拷贝(复制)构造函数" <<endl; len = st.len; str = new char[len+1]; std::strcpy(str,st.str); num_strings ++; } String::~String() { --num_strings; delete [] str; } // 成员运算符 String & String::operator=(const String & s) { if(this == &s) { return *this; } delete [] str; len = s.len; str = new char[len +1]; std::strcpy(str,s.str); return * this; } String & String::operator=(const char * s) { delete [] str; len = std::strlen(s); str = new char[len + 1]; std::strcpy(str,s); return *this; } // 元素可读可写 char & String::operator[](int i) { return str[i]; } // 元素只读 const char & String::operator[](int i) const { return str[i]; } // 重载运算符友元函数 bool operator<(const String &str1, const String &str2) { return std::strcmp(str1.str, str2.str) < 0; } bool operator>(const String &str1, const String &str2) { return str2 < str1; } bool operator==(const String &str1, const String &str2) { return std::strcmp(str1.str, str2.str) == 0; } ostream & operator<< (ostream & os, const String &str) { os << str.str; return os; } istream & operator>>(istream & is, String &str) { char temp[String::CINLIM]; is.get(temp,String::CINLIM); if (is) str = temp; // 如果到达文件尾或get(char *, int)读取的是一个空行,导致输入失败, istream的值将置为false while(is && is.get() !='\n') continue; return is; }
sayings1.cpp 主文件
#include <iostream> #include "string1.h" const int ArSize = 10; const int Maxlen = 81; int main(int argc, char const *argv[]) { using namespace std; String name; // name = "hello"; cout << "Enter your name: " ; cin >> name; cout << '\n'; cout << "your name is :"<< name; String saying[ArSize]; char temp[Maxlen]; int i; for(i = 0; i < ArSize; i++) { cout << i +1 << ":"; cin.get(temp, Maxlen); while(cin && cin.get() != '\n') { continue; } if(!cin || temp[0] == '\0') { break; } else { saying[i] = temp; } } int total = i; if(total > 0) { cout << "Here are your sayings: \n" ; for(i =0 ; i< total; i++) { cout << saying[i][0] << ": " << saying[i] << endl; } int shortest = 0; int first = 0; for(i = 1; i < total ; i ++) { if(saying[i].length() < saying[shortest].length()) { shortest = i; } if(saying[i] < saying[first]) { first = i ; } } cout << "Shortest saying :\n" << saying[shortest] << endl; cout << "First alphabetically :\n" << saying[first] << endl; cout << "This program used " << String ::HowMany() << "String objects. Bye.\n"; } else{ cout << "No Input! Bye. \n"; } system("pause"); return 0; }
注意: 较早的get(chat *, int)版本在读取空行后,返回的值不为false。 然而,对于这些版本来说, 如果读取的是一个空行, 则字符串中第一个字符将是一个空字符。
if(!cin || temp[0] == '\0') { break; } // if语句中第一个条件检测空行, 第二个条件用于旧版本的实现中检测空行。
#ifndef A_H_ #define A_H_ #include <iostream> using namespace std; class A { public: A(){ cout << "A default constructor"<< endl; } A(const A & a) { cout << "A copy constructor"<< endl; } }; #endif A test( A & a) { return a; } int main(int argc, char const *argv[]) { A a; test(a); return 0; }
输出:
A default constructor
A copy constructor
#include <iostream> using namespace std; class A { private: int a; int b; public: A(); A(int a, int b); A operator=(const A & a); A(const A &a); void test(); }; A::A() { cout << "default constructor \n"; this-> a= 0; this->b = 0; } A::A(int a, int b) { cout << a << " \t "<< b<<endl; this -> a = a; this->b = b; } A A::operator=(const A & a) { cout << "copy" << endl; return a; } A::A(const A & a) { cout << "copy constructor" << endl; } void A::test() { cout << this->a << " \t" << this->b << endl; } A test() { A a; cout << typeid(a).name() << endl; // 1A return a; } int main(int argc, char const *argv[]) { A a(2, 3); A b = test(); // 底层做了优化 并没有调用复制构造函数 cout << typeid(b).name() << endl; // 1A b.test(); return 0; }
定位new运算符
定位new 运算符可以在分配内存时能够指定内存位置
#include <iostream> #include <string> #include <new> using namespace std; const int BUF = 512; class JustTesting { private: string words; int number; public: JustTesting(const string & s = "Justing Testing", int n = 0) {words = s; number = n; cout << words << " constructed. \n";} ~JustTesting(){cout << words << " destroyed\n";} void show() const { cout << words << ", " << number << endl; } }; int main(int argc, char const *argv[]) { char * buffer = new char[BUF]; JustTesting *pc1, *pc2; pc1 = new (buffer)JustTesting; // 在buffer中放置对象 pc2 = new JustTesting("Heap1",20); // 在堆中放置对象 cout << "Memory block address : \n" <<"buffer: "<< (void *)buffer << " heap: " << pc2 << endl; cout << "Memory contents: \n" ; cout << pc1 << ": "; pc1 ->show(); cout << pc2 << ": "; pc2 ->show(); JustTesting *pc3, *pc4; pc3 = new (buffer)JustTesting("Bad Idea",6); pc4 = new JustTesting("heap2", 10); cout << "Memory contents: \n" ; cout << pc3 << ": "; pc3 ->show(); cout << pc4 << ": "; pc4->show(); delete pc2; // 释放 heap1 delete pc4; // 释放 heap2 delete [] buffer; cout << "Done \n"; return 0; }
使用定位new运算符时存在两个问题:1. 首先在创建第二个对象时, 定位new 运算符使用一个新对象来覆盖用于第一个对象的内存单元。2.
其次, 将delete用于pc2和pc4时, 将自动调用pc2和pc4指向对象调用的析构函数, 然而使用, 将delete[] 用于buffer时, 不会为使用定位new运算符创建的对象调用析构函数。
解决方法: 1. 要使用不同的内存单元, 内存单元不能发生重叠。需要提供两个位于缓冲区的不同的地址, 并确保这两个内存单元不重叠。
例如,可以这样做:
pc1 = new (buffer)JustTesting; pc2 = new (buffer + sizeof(JustTesting))JustTesting("Better Idea",6);
delete运算符可以与常规的new运算符配合使用,但不能与定位new运算符配合使用。
问题2的解决方案是,显式地为使用定位new 运算符创建的对象调用析构函数。需要显式地调用析构函数。
pc3->~JustTesting(); // 销毁pc3指向的对象 pc1->~JustTesting(); // 销毁pc3指向的对象
注意正确的删除顺序,对于使用定位new运算符创建的对象,应以创建顺序相反的顺序进行删除。原因是,晚创建的对象可以依赖于早创建的对象。另外, 仅当所有对象都被销毁过后, 才能释放用于存储这些对象的缓冲区。
修复后的代码
#include <iostream> #include <string> #include <new> using namespace std; const int BUF = 512; class JustTesting { private: string words; int number; public: JustTesting(const string & s = "Justing Testing", int n = 0) {words = s; number = n; cout << words << " constructed. \n";} ~JustTesting(){cout << words << " destroyed\n";} void show() const { cout << words << ", " << number << endl; } }; int main(int argc, char const *argv[]) { char * buffer = new char[BUF]; JustTesting *pc1, *pc2; pc1 = new (buffer)JustTesting; // 在buffer中放置对象 pc2 = new JustTesting("Heap1",20); // 在堆中放置对象 cout << "Memory block address : \n" <<"buffer: "<< (void *)buffer << " heap: " << pc2 << endl; cout << "Memory contents: \n" ; cout << pc1 << ": "; pc1 ->show(); cout << pc2 << ": "; pc2 ->show(); JustTesting *pc3, *pc4; pc3 = new (buffer)JustTesting("Bad Idea",6); pc4 = new JustTesting("heap2", 10); cout << "Memory contents: \n" ; cout << pc3 << ": "; pc3 ->show(); cout << pc4 << ": "; pc4->show(); delete pc2; // 释放 heap1 delete pc4; // 释放 heap2 pc3->~JustTesting(); pc1->~JustTesting(); delete [] buffer; cout << "Done! \n"; return 0; }
队列模拟
要求:
限制等待人数
对顾客排队等待时间进行预测
1/3的顾客只需要1分钟就能获得服务,1/3的顾客需要3分钟,另外1/3的顾客需要3分钟。顾客到达的时间是随机的,但每个小时使用ATM的顾客数量相当稳定。 工程的另外两个任务是: 设计一个表示顾客的类; 编写一个程序来模拟顾客和队列之间的交互。
队列类
- 队列存储有序的项目队列
- 队列能容纳的项目数有一定的限制
- 应当能够创建空队列
- 应当能够检查队列是否为空
- 应该能够检查队列是否为满
- 能够在队尾添加项目
- 在队首删除项目
- 应当能够确定队列中项目数
在类声明中声明的结构、类或枚举被称为是被嵌套在类中, 其作用域为整个类。这种声明不会创建数据对象, 而只是指定了可以在类中使用的类型。
如果声明时在类的私有部分进行的, 则只能在这个类使用被声明的类型;如果声明是在公有部分进行的, 则可以从类的外部通过作用域解析运算符使用被声明的类型。例如: 如果Node是在Queue类的公有部分声明的,则可以在类的外面声明Queue::Node类型的变量。
对于const数据成员,必须在执行到构造函数之前,即创建对象时进行初始化。使用成员初始化列表,效率更高。
ATM模拟:
程序允许用户输入3个数: 队列的最大长度、程序模拟的持续性时间(单位为小时)以及平均每小时的客户数。
- 判断是否来了新客户,如果来了,且队列没有满,则将它添加到队列中,否则拒绝客户入队。
- 如果没有客户在进行交易,则选取队列中的第一个客户。 确定该客户的已等候的时间,并将wait_time计数器设置为新客户所需的处理时间。
- 如果客户正在处理中,则将wait_time计算器减1
- 记录各种数据,如获得服务的客户数目、被拒绝的客户数目、排队等候的累积时间以及累积的队列长度。、
RAND_MAX是rand()函数返回的最大值(cstdlib.h)
classy::Classy(int n , int m):mem1(n), mem2(0),mem3(n*m + 2) { } // 这种格式只能用于构造函数 // 必须用这种格式来初始化非静态const数据成员 // 必须用这种格式初始化引用数据成员 数据成员初始化顺序与它们出现在类声明中的顺序相同, 与初始化器中的排列顺序无关。 C++ 11 允许 在类声明成员属性的时候,直接赋值。 这与使用初始化列表等价。
queue.h头文件
#ifndef QUEUE_H_ #define QUEUE_H_ class Customer { private: long arrive; // 客户到达的时间 int processtime; // 交易处理的时间 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 ; // 当前队列中items数 const int qsize; // 当前队列中items的最大数目 Queue(const Queue & q):qsize(0){} Queue & operator=(const Queue & q){return *this;} public: Queue(int qs = Q_SIZE); // 创建一个队列, 队列的长度最大为Q_SIZE ~Queue(); bool isempty() const; bool isfull() const; int queuecount() const ; bool enqueue(const Item &item); // 将item添加到对尾 bool dequeue(Item & item); // 从队头移除item }; #endif
queue.cpp文件
#include "queue.h" #include <cstdlib> // rand() 函数 头文件 #include <iostream> Queue::Queue(int qs):qsize(qs) { front = rear = NULL; items = 0; } Queue::~Queue() { Node * temp; while(front != NULL) { temp = front; front = front->next; delete temp; } } bool Queue::isempty() const { return items == 0; } bool Queue::isfull() const { return items == Q_SIZE; } 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; items ++; if(front == NULL) { front = add; }else { rear->next = add; } rear = add; return true; } bool Queue::dequeue(Item &item) // 出队 { if(front == NULL) { return false; } item = front -> item; // 队头 items --; Node *temp = front; front = front -> next; delete temp; if(items == 0 ) { rear = NULL; } return true; } void Customer::set(long when) { processtime = std::rand() % 3 + 1; // 模拟处理时间 std::cout << "processtime="<<processtime << std::endl; arrive = when; }
bank.cpp文件
#include <iostream> #include <cstdlib> #include <ctime> #include "queue.h" using namespace std; const int MIN_PER_HR = 60; // 60分钟 1小时 bool newcustomer(double x); // 是否来了新客户 int main(int argc, char const *argv[]) { srand(time(0)); int qs; // 输入队列的最大元素个数 cout << "Enter max size of queue: "; cin >> qs; Queue line(qs); // 输入模拟的小时数 cout << "Enter the number of simulation hours: " ; int hours ; cin >> hours; // 每分钟执行一次模拟 long cyclelimit = MIN_PER_HR * hours; // 一个小时60分钟 一共多少分钟 // 输入每小时客户的平均数量 cout <<"Enter the average number of customers per hour" <<endl; double perhour; cin >> perhour; double min_per_cust ; // 两次到达的平均时间 min_per_cust = MIN_PER_HR / perhour; Item temp; // 新的用户数据 long turnways = 0; // 拒绝客户数 long customers = 0 ; // 加入到队列数 long served = 0; // 服务客户数 long sum_line = 0; // 累计队伍长度 int wait_time = 0; // 单个用户的等待服务的时间 long line_wait = 0; // 排队等候的累计时间 // 运行模拟 for(int cycle = 0; cycle < cyclelimit ; cycle ++) // 1分钟循环一次 平均每6分钟来1次顾客 { if(newcustomer(min_per_cust)) { if(line.isfull()) { turnways ++ ; }else { customers ++ ; temp.set(cycle); // 设置到达的时间 line.enqueue(temp); // 入队 } if(wait_time <= 0 && !line.isempty()) // 对列不空 { line.dequeue(temp); wait_time = temp.ptime(); // 等待时间 line_wait += cycle - temp.when(); served ++ ; } if(wait_time > 0) { wait_time -- ; } sum_line += line.queuecount(); } } // 报告结果 if(customers > 0) { cout << "customers accepted:" << customers << endl; cout << "customers served:" << served << endl; cout << "turnaways: " << turnways <<endl; cout << "average queue size: " ; cout.precision(2); cout.setf(ios_base::fixed, ios_base::floatfield); cout << (double)sum_line/cyclelimit << endl; cout << "average wait time: " << (double) line_wait /served << " minutes\n" ; } else { cout << "No customers! \n" << endl; } cout << "Done! \n" ; return 0; } bool newcustomer(double x) { return (rand() * x / RAND_MAX) < 1; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· .NET Core 中如何实现缓存的预热?
· 三行代码完成国际化适配,妙~啊~
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?