11.10 类与动态内存分配

通常,最好是在程序运行时(而不是编译时)确定诸如使用多少内存等问题。对于在对象中存储姓名来说,通常的C++方法是,在类构造函数中使用new运算符在程序运行时分配所需的内存。为此,通常的方法是使用string类,它将为您处理内存管理细节。下面将深入讨论动态内存分配。

11.10.1 动态内存分配和类

C++在内存分配时采取的策略为,让程序在运行时决定内存分配,而不是在编译时决定。

在构造函数中使用new来为字符串分配空间,这避免了在类声明中预先定义字符串的长度。

11.10.1.1 静态数据成员

静态数据成员在类声明中声明,在包含类方法的文件中初始化。初始化时使用作用域运算符来指出静态成员所属的类。但如果静态成员是整型或枚举型常量,则可以在类声明中初始化(初始化是在方法文件中,在包含类方法的文件中初始化,这是因为类声明位于头文件中,可能会多次包含该头文件,导致出现多个初始化语句副本出错。)

11.10.1.2 new 和delete

在构造函数中使用new来分配内存时,必须在相应的析构函数中使用delete来释放内存。如果使用new []来分配内存,则应使用delete []来释放内存。

11.10.2 特殊成员函数

下面这些成员函数是自动定义的

  • 默认构造函数,如果没有定义的构造函数
  • 默认析构函数,如果没有定义
  • 复制构造函数,如果没有定义
  • 赋值运算符,如果没有定义
  • 地址运算符,如果没有定义

更准确地说,编译器将生成上述最后三个函数的定义—如果程序使用对象的方式要求这样做。

隐式地址运算符返回调用对象的地址(即this指针)。这与我们的初衷是一致的,在此不详细讨论该成员函数。

11.10.2.1 默认构造函数

如果没有提供任何构造函数,C++将创建默认构造函数。

11.10.2.1.1 自动构造函数

默认构造函数如下:

Klunk::Klunk() { } // implicit default constructor

就算自定义了构造函数,也需要定义一个默认构造函数:因为创建对象时总是会调用默认构造函数:

Klunk lunk; // invokes default constructor

默认构造函数使Lunk类似于一个常规的自动变量,也就是说,它的值在初始化时是未知的。

11.10.2.1.2 在函数体初始化成员变量的构造函数

默认构造函数的特征就是没有形参,没有返回值,所以在默认构造函数内部初始化成员变量的值也是允许的:

Klunk::Klunk() // explicit default constructor
{klunk_ct = 0;...
}
11.10.2.1.3 有默认形参的构造函数

带参数的构造函数也可以是默认构造函数,只要所有参数都有默认值。

Klunk(int n = 0) { klunk_ct = n; }
11.10.2.1.4 注意事项

前述三个只能任选其一。

11.10.2.2 复制构造函数

复制构造函数用于将一个对象复制到新创建的对象中。也就是说,它用于初始化过程中(包括按值传递参数),而不是常规的赋值过程中。类的复制构造函数原型通常如下:

Class_name(const Class_name &);
11.10.2.2.1 什么时候使用复制构造函数

新建一个对象并将其初始化为同类现有对象时,复制构造函数都将会被调用。

最常见的情况是将新对象显式地初始化为现有的对象。

StringBad ditto(motto); // calls StringBad(const StringBad &)
StringBad metoo = motto; // calls StringBad(const StringBad &)
StringBad also = StringBad(motto);// calls StringBad(const StringBad &)
StringBad * pStringBad = new StringBad(motto);// calls StringBad(const StringBad &)

每当程序生成了对象副本时,编译器都将使用复制构造函数。

具体地说,当函数按值传递对象或函数返回对象时,都将使用复制构造函数。

编译器生成临时对象时,也将使用复制构造函数。

由于按值传递对象将调用复制构造函数,因此应该按引用传递对象。这样可以节省调用构造函数的时间以及存储新对象的空间。

11.10.2.2.2 默认复制构造函数的功能

默认的复制构造两数逐个复制非静态成员(成员复制也称为浅复制),复制的是成员的值。
提示:如果类中包含这样的静态数据成员,即其值将在新对象被创建时发生变化,则应该提供一个显式复制构造西数来处理计数问題。
警告:如果类中包含了使用new初始化的指针成员,应当定义一个复制构造函教,以复制指向的数据,而不是指针,这被称为深度复制。复制的另一种形式(成员复制或浅复制)只是复制指针值。浅复制仅浅浅地复制指针信息,而不会深入“挖掘” 以复制指针引用的结构。

11.10.2.2.3 复制构造函数格式
//这是复制构造函数
StringBad::StringBad(const StringBad& st)
{cout << "Copy Contructor is Called!\n";num_strings++; // handle static member updatelen = st.len; // same lengthstr = new char[len + 1]; // allot spacestrcpy_s(str,len+1, st.str); // copy string to new locationcout << num_strings << ": \"" << str<< "\" object created\n"; // For Your Information
}

11.10.2.3 赋值运算符

赋值运算符的功能即何时使用它

将已有的对象赋给另一个对象时,将使用重载的赋值运算符。

StringBad knot;
knot = headline1; // assignment operator invoked

与复制构造函数相似,赋值运算符的隐式实现也对成员进行逐个赋值。如果成员本身就是类对象,则程序将使用为这个类定义的赋值运算符来复制改成员,但静态数据成员不受影响。

11.10.3 静态类成员函数

可以将成员函数声明为静态,这有两个重要的效果:

  • 首先,静态成员函数不必由对象调用;实际上,它甚至没有这个指针来操作。如果static成员函数是在public部分声明的,则可以使用类名和作用域解析操作符调用它。
  • 第二个结果是,由于静态成员函数与特定对象没有关联,因此它只能使用静态数据成员。

静态成员函数可用于设置类范围的标志,该标志控制类接口的某些方面的行为。例如,它可以控制显示类内容的方法所使用的格式。

11.10.4 String类举例—针对前述知识点

code:

  • strngbad.h
#pragma once
// strngbad.h -- flawed string class definition
#include <iostream>
using std::istream;
using std::ostream;
#ifndef STRNGBAD_H_
#define STRNGBAD_H_
class StringBad
{private:char* str; // pointer to stringint len; // length of string//In general, a string class doesn’t need such a member of num_strings.//在这里使用num_strings是为了指出本程序潜在的问题static int num_strings; // number of objects static const int CINLIM = 80; // cin input limit
public:StringBad(const char* s); // constructorStringBad(); // default constructorStringBad(const StringBad& s);//复制构造函数~StringBad(); // destructorint length() const { return len; }StringBad& operator=(const StringBad& st);StringBad& operator=(const char* s);char& operator[](int i);const char& operator[](int i) const;friend bool operator<(const StringBad& st1, const StringBad& st2);friend bool operator>(const StringBad& st1, const StringBad& st2);friend bool operator==(const StringBad& st1, const StringBad& st2);friend std::ostream& operator<<(std::ostream& os, const StringBad& st);friend istream& operator>>(istream& is, StringBad& st);static int HowMany();
};
#endif
  • strngbad.cpp
// strngbad.cpp -- StringBad class methods
#include <cstring> // string.h for some
#include "strngbad.h"
using std::cout;
using std::cin;
// initializing static class member
//Note that the initialization statement gives the type and uses the scope operator, but it doesn’t use the static keyword.
int StringBad::num_strings = 0;
// class methods
// static method
int StringBad::HowMany()
{return num_strings;
}
// construct StringBad from C string
StringBad::StringBad(const char* s)
{len = std::strlen(s); // set sizestr = new char[len + 1]; // allot storagestrcpy_s(str, len+1, s); // initialize pointernum_strings++; // set object countcout << num_strings << ": \"" << str<< "\" object created\n"; // For Your Information
}
StringBad::StringBad() // default constructor
{num_strings++; // set object countlen = 0;str = new char[1];str[0] = '\0'; // default string
}//这是复制构造函数
StringBad::StringBad(const StringBad& st)
{cout << "Copy Contructor is Called!\n";num_strings++; // handle static member updatelen = st.len; // same lengthstr = new char[len + 1]; // allot spacestrcpy_s(str,len+1, st.str); // copy string to new locationcout << num_strings << ": \"" << str<< "\" object created\n"; // For Your Information
}StringBad::~StringBad() // necessary destructor
{cout << "\"" << str << "\" object deleted, "; // FYI--num_strings; // requiredcout << num_strings << " left\n"; // FYIdelete[] str; // required
}StringBad& StringBad::operator=(const StringBad& st)
{cout << "Operator= is Called!\n";if (this == &st) // object assigned to itselfreturn *this; // all donedelete[] str; // free old stringlen = st.len;str = new char[len + 1]; // get space for new stringstrcpy_s(str, len + 1, st.str); // copy the stringreturn *this; // return reference to invoking object
}StringBad& StringBad::operator=(const char* s)
{delete[] str;len = std::strlen(s);str = new char[len + 1];strcpy_s(str, len+1, s);return *this;
}// read-write char access for non-const String
char& StringBad::operator[](int i)
{return str[i];
}
// read-only char access for const String
const char& StringBad::operator[](int i) const
{return str[i];
}bool operator<(const StringBad& st1, const StringBad& st2)
{return (std::strcmp(st1.str, st2.str) < 0);
}bool operator>(const StringBad& st1, const StringBad& st2)
{return st2 < st1;
}bool operator==(const StringBad& st1, const StringBad& st2)
{return (std::strcmp(st1.str, st2.str) == 0);
}std::ostream& operator<<(std::ostream& os, const StringBad& st)
{os << st.str;return os;
}// quick and dirty String input
istream& operator>>(istream& is, StringBad& st)
{char temp[StringBad::CINLIM];is.get(temp, StringBad::CINLIM);if (is)st = temp;while (is && is.get() != '\n')continue;return is;
}
  • main.cpp
// vegnews.cpp -- using new and delete with classes
// compile with strngbad.cpp
#include <iostream>
#include "strngbad.h"
using std::cout;
const int ArSize = 10;
const int MaxLen = 81;
void callme1(StringBad&); // pass by reference
void callme2(StringBad); // pass by valueint main()
{using std::cout;using std::cin;using std::endl;{cout << "\nStarting an inner block 1.\n";StringBad headline1("Celery Stalks at Midnight");StringBad headline2("Lettuce Prey");StringBad sports("Spinach Leaves Bowl for Dollars");cout << "headline1: " << headline1 << endl;cout << "headline2: " << headline2 << endl;cout << "sports: " << sports << endl;callme1(headline1);cout << "headline1: " << headline1 << endl;callme2(headline2);cout << "headline2: " << headline2 << endl;cout << "Initialize one object to another:\n";StringBad sailor = sports;cout << "sailor: " << sailor << endl;cout << "Assign one object to another:\n";StringBad knot;cout << "knot: " << knot << endl;knot = headline1;cout << "knot: " << knot << endl;cout << "Exiting the block 1.\n";}{cout << "\nStarting an inner block 2.\n";StringBad name;cout << "Hi, what's your name?\n>> ";cin >> name;cout << name << ", please enter up to " << ArSize<< " short sayings <empty line to quit>:\n";StringBad sayings[ArSize]; // array of objectschar temp[MaxLen]; // temporary string storageint 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') // empty line?break; // i not incrementedelsesayings[i] = temp; // overloaded assignment}int total = i; // total # of lines readif (total > 0){cout << "Here are your sayings:\n";for (i = 0; i < total; i++)cout << sayings[i][0] << ": " << sayings[i] << endl;int shortest = 0;int first = 0;for (i = 1; i < total; i++){if (sayings[i].length() < sayings[shortest].length())shortest = i;if (sayings[i] < sayings[first])first = i;}cout << "Shortest saying:\n" << sayings[shortest] << endl;;cout << "First alphabetically:\n" << sayings[first] << endl;cout << "This program used " << StringBad::HowMany()<< " String objects. Bye.\n";}elsecout << "No input! Bye.\n";cout << "Exiting the block 2.\n";}cout << "End of main()\n";return 0;
}
void callme1(StringBad& rsb)
{cout << "String passed by reference:\n";cout << " \"" << rsb << "\"\n";
}
void callme2(StringBad sb)
{cout << "String passed by value:\n";cout << " \"" << sb << "\"\n";
}

运行结果:

String passed by reference:"Celery Stalks at Midnight"
headline1: Celery Stalks at Midnight
Copy Contructor is Called!
4: "Lettuce Prey" object created
String passed by value:"Lettuce Prey"
"Lettuce Prey" object deleted, 3 left
headline2: Lettuce Prey
Initialize one object to another:
Copy Contructor is Called!
4: "Spinach Leaves Bowl for Dollars" object created
sailor: Spinach Leaves Bowl for Dollars
Assign one object to another:
knot:
Operator= is Called!
knot: Celery Stalks at Midnight
Exiting the block 1.
"Celery Stalks at Midnight" object deleted, 4 left
"Spinach Leaves Bowl for Dollars" object deleted, 3 left
"Spinach Leaves Bowl for Dollars" object deleted, 2 left
"Lettuce Prey" object deleted, 1 left
"Celery Stalks at Midnight" object deleted, 0 leftStarting an inner block 2.
Hi, what's your name?
>> Jasmine
Jasmine, please enter up to 10 short sayings <empty line to quit>:
1: Boooooo
2: Jasmine
3: Boly
4: Lily
5: Petrichor
6: Bose
7: Sony
8: Ipad
9: Iphone
10: Mua~
Here are your sayings:
B: Boooooo
J: Jasmine
B: Boly
L: Lily
P: Petrichor
B: Bose
S: Sony
I: Ipad
I: Iphone
M: Mua~
Shortest saying:
Boly
First alphabetically:
Boly
This program used 11 String objects. Bye.
Exiting the block 2.
"Mua~" object deleted, 10 left
"Iphone" object deleted, 9 left
"Ipad" object deleted, 8 left
"Sony" object deleted, 7 left
"Bose" object deleted, 6 left
"Petrichor" object deleted, 5 left
"Lily" object deleted, 4 left
"Boly" object deleted, 3 left
"Jasmine" object deleted, 2 left
"Boooooo" object deleted, 1 left
"Jasmine" object deleted, 0 left
End of main()D:\Prj\C++\Dynamic_Memory_String\Debug\Dynamic_Memory_String.exe (进程 3792)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .

11.10.5 在构造函数中使用new的注意事项

11.10.5.1 类构造函数使用new

  • Any class member that points to memory allocated by new should have the delete operator applied to it in the class destructor.This frees the allocated memory.
  • If a destructor frees memory by applying delete to a pointer that is a class member, every constructor for that class should initialize that pointer, either by using new or by setting the pointer to the null pointer.
  • Constructors should settle on using either new [] or new, but not a mixture of both.The destructor should use delete [] if the constructors use new [], and it should use delete if the constructors use new.
  • You should define a copy constructor that allocates new memory rather than copying a pointer to existing memory.This enables a program to initialize one class object to another.The constructor should normally have the following prototype:
    className(const className &)
  • You should define a class member function that overloads the assignment operator and that has a function definition.

11.10.5.2 使用new的注意事项

  • 如果使用new初始化构造函数中的指针成员,则应在析构函数中使用delete。
  • new和delete的用法应该兼容。您应该将new与delete配对,将new[]与delete[]配对。
  • 如果有多个构造两数,则必须以相同的方式使用new,要么都带中括号,要么都不带。因为只有一个析构两数,所有的构造两数都必须与它兼容。然而,可以在一个构造函数中使用 new 初始化指针,而在另一个构造两数中将指针初始化为空(0或 C++11 中的nullptr),这是因为 delete(无论是带中括号还是不带中括号)可以用于空指针。

11.10.5.3 NULL 或0或nullptr?

NULL、0还是nullptr:以前,空指针可以用0 或NULL(在很多头文件中,NULL是一个被定义为0的符号常量)来表示。C程序员通常使用NULL 而不是0,以指出这是一个指针,就像使用’\0’而不是0来表示空宇符,以指出这是一个宇符一样。然而,C++传统上更喜欢用简单的0,而不是等价的NULL。但正如前面指出的,C++11提供了关键宇nullptr,这是一种更好的选择

11.10.5.4 应该和不应该

构造函数:

String::String()//NOT OK
{str = "default string"; // oops, no new []len = std::strlen(str);
}
String::String(const char * s)//NOT OK
{len = std::strlen(s);str = new char; // oops, no []std::strcpy(str, s); // oops, no room
}
String::String(const String & st)//OK
{len = st.len;str = new char[len + 1]; // good, allocate spacestd::strcpy(str, st.str); // good, copy value
}
String::String()//OK
{len = 0;str = new char[1]; // uses new with []str[0] = '\0';
}
String::String()//OK
{len = 0;str = 0; // or, with C++11, str = nullptr;OK
}
String::String()//OK
{static const char * s = "C++"; // initialized just oncelen = std::strlen(s);str = new char[len + 1]; // uses new with []std::strcpy(str, s);
}

析构函数:

String::~String()//NOT OK
{delete str; // oops, should be delete [] str;
}
String::~String()//OK
{delete[] str;
}

11.10.5.5 类成员的逐成员复制

class Magazine
{private:String title;string publisher;...
};

String和String都使用动态内存分配。这是否意味着您需要为Magazine类编写复制构造函数和赋值操作符?不

默认的逐成员复制和赋值行为有一定的智能。如果您将一个Magazine 对象复制或赋值给另一个 Magazine 对象,逐成员复制将使用成员类型定义的复制构造两数和赋值运算符。也就是说,复制成员 title 时,将使用 String 的复制构造两数,而将成员 title 赋给另一个 Magazine对象时,将使用 String 的赋值运算符,依此类推。然市,如果 Magazinc 类因其他成员需要定义复制构造函数和赋值运算符,情况将更复杂:在这种情况下,这些两数必须显式地调用 String 和 string 的复制构造函数和赋值运算符。

11.10.6 有关返回对象的说明

11.10.6.1 返回const对象的引用

如果函数返回(调用对象或参数对象)传递给他的对象,可以通过返回引用来提高效率。

以下两种实现都有效:

// version 1
Vector Max(const Vector & v1, const Vector & v2)
{if (v1.magval() > v2.magval())return v1;elsereturn v2;
}
// version 2
const Vector & Max(const Vector & v1, const Vector & v2)
{if (v1.magval() > v2.magval())return v1;elsereturn v2;
}

有三个要点:

  • 首先,返回对象将调用复制构造函数,而返回引用不会。因此,第二个版本效率更高。
  • 其次,引用对象指向的对象应该在调用函数执行时存在。
  • 第三,v1和v2都被声明为const引用,因此返回类型必须为const,这样才匹配。

11.10.6.2 返回非const对象的引用

两种常见的返回非const对象的情形是,重载赋值运算符以及重载与cout一起使用的<<运算符。

11.10.6.2.1 重载=操作符

operator=()的返回值用于连续赋值:

String s1("Good stuff");
String s2, s3;
s3 = s2 = s1;

在上述代码中,s2.operator=()的返回值被赋给s3。为此,返回 String 对象或 String 对象的引用都是可行的,但与 Vector 示例中一样,通过使用引用,可避免该两数调用 String 的复制构造两数来创建一个新的String 对象。在这个例子中,返回类型不是const,因为方法 operator=0返回一个指向s2 的引用,可以对其进行修收。

11.10.6.2.2 重载<<操作符

operator<<()的返回值是为了串联输出:

String s1("Good stuff");
cout << s1 << "is coming!";

在上述代码中,operator<< (cout,s1)的返回值成为一个用于显示字符串“is coming!” 的对象。返回类型必须是 ostream &,而不能仅仅是ostream。如果使用返回类型ostream,将要求调用 ostream 类的复制构造函数,而ostream 没有公有的复制构造两数。幸运的是,返回一个指向 cout 的引用不会带来任何问题,因为 cout 已经在调用两数的作用域内。

11.10.6.3 返回一个对象

如果被返回的对象是被调用两数中的局部变量,则不应按引用方式返回它,因为在被调用两数执行完华时,局部对象将调用其析构函数。因此,当控制权回到调用两数时,引用指向的对象将不再存在。在这种情况下,应返回对象而不是引用。通常,被重载的算术运算符属于这一类。

Vector force1(50,60);
Vector force2(10,70);
Vector net;
net = force1 + force2;Vector Vector::operator+(const Vector & b) const
{return Vector(x + b.x, y + b.y);
}

总之,如果方法或两数要返回局部对象,则应返回对象,而不是指向对象的引用。在这种情况下,将使用复制构造函数来生成返回的对象。如果方法或两数要返回一个没有公有复制构造两数的类(如 ostream类)的对象,它必须返回一个指向这种对象的引用。

11.10.6.4 返回const对象

正常情况下是这样使用的:

net = force1 + force2;

但是呢,程序员在编写程序的过程中可能会出现下面这样的语句,这是不应该允许的,因为可能会运行出错:

force1 + force2 = net;
cout << (force1 + force2 = net).magval() << endl;

因此:

r如果您担心这种行为可能引发的误用和滥用,有一种简单的解决方案:将返回类型声明为const Vector。

11.10.7 将指针用于对象

11.10.7.1 何时调用析构函数?

  • 如果对象是动态变量,则当执行完定义该对象的程序块时,将调用该对象的析构函数。
  • 如果对象是静态变量(外部、静态、静态外部或来自名称空间),则在程序结束时将调用对象的析构函数。
  • 如果对象是用 new 创建的,则仪当您最式使用 delete 刷除对象时,其析构两数才会被调用。

11.10.7.2 指针和对象总结


11.10.7.3 置换new

Code:

// placenew1.cpp -- new, placement new, no delete
#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 = "Just 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()
{char * buffer = new char[BUF]; // get a block of memoryJustTesting *pc1, *pc2;pc1 = new (buffer) JustTesting; // place object in bufferpc2 = new JustTesting("Heap1", 20); // place object on heapcout << "Memory block addresses:\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; // free Heap1delete pc4; // free Heap2delete [] buffer; // free buffercout << "Done\n";return 0;
}

上述代码使用置换new运算符存在两个问题:

首先,在创建第二个对象时,定位 new 运算符使用一个新对象来覆盖用于第一个对象的内存单元。显然,如果类动态地为其成员分配内存,这将引发问题。
其次,将delete 用于 pc2和pc4 时,将自动调用为 pc2 和pc4 指向的对象调用析构函数:然而,将delete用于buffer 时,不会为使用定位new 运算符创建的对象调用析构两数。
程序员必须负责缓冲区内存单元分配。要使用不同的内存单元,程序员需要提供两个位于缓冲区的不同地址,并确保这两个内存单元不重叠。

pc1 = new (buffer) JustTesting;
pc3 = new (buffer + sizeof (JustTesting)) JustTesting("Better Idea", 6);

其中指针pc3相对于pc1的偏移量为JustTesting对象的大小。

如果使用定位new运算符来对对象分配内存,必须确保其析构函数被调用。

delete pc2; // delete object pointed to by pc2

但是不能这样:

delete pc1; // delete object pointed to by pc1? NO!
delete pc3; // delete object pointed to by pc3? NO!

原因在于delete可与常规new运算符配合使用,但不能与定位new 运算符配合使用。例如,指针 pc3 没有收到new运算符返回的地址,因此delete pc3 将导致运行阶段错误。在另一方面,指针pc1指向的地址与buffer 相同,但 buffer 是使用new[]初始化的,因此必须使用 delete []而不是delete来释放。即使buffer 是使用new 而不是new[]初始化的,delete pc1也将释放buffer,而不是pc1。这是因为 new/delete 系统知道已分配的 512字节块buffer,但对定位 new运算符对该内存块做了何种处理一无所知。

该程序确实释放了buffer:

delete [] buffer; // free buffer

显式地为使用定位new运算符创建的对象调用析构函数。正常情况下将自动调用析构函数,但是需要显式调用析构函数的少数情形之一。显式地调用析构函数时,必须指定要销毁的对象。

pc3->~JustTesting(); // destroy object pointed to by pc3
pc1->~JustTesting(); // destroy object pointed to by pc1

对于使用定位new运算符创建的对象,应该与创建循序相反的顺序进行删除。原因在于,晚创建的对象可能依赖于早创建的对象。另外,仅当所有对象都被销毁后,才能释放用于存储这些对象的缓冲区。

修改后的版本:

// placenew2.cpp -- new, placement new, no delete
#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 = "Just 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()
{char * buffer = new char[BUF]; // get a block of memoryJustTesting *pc1, *pc2;pc1 = new (buffer) JustTesting; // place object in bufferpc2 = new JustTesting("Heap1", 20); // place object on heapcout << "Memory block addresses:\n" << "buffer: "<< (void *) buffer << " heap: " << pc2 <<endl;cout << "Memory contents:\n";cout << pc1 << ": ";pc1->Show();cout << pc2 << ": ";pc2->Show();JustTesting *pc3, *pc4;// fix placement new locationpc3 = new (buffer + sizeof (JustTesting))JustTesting("Better Idea", 6);pc4 = new JustTesting("Heap2", 10);cout << "Memory contents:\n";cout << pc3 << ": ";pc3->Show();cout << pc4 << ": ";pc4->Show();delete pc2; // free Heap1delete pc4; // free Heap2// explicitly destroy placement new objectspc3->~JustTesting(); // destroy object pointed to by pc3pc1->~JustTesting(); // destroy object pointed to by pc1delete [] buffer; // free buffercout << "Done\n";return 0;
}

11.10.8 嵌套类型

11.10.8.1 简介

C++允许将结构体、类和枚举定义在类声明中。

class Queue
{private:// class scope definitions// Node is a nested structure definition local to this classstruct Node { Item item; struct Node * next;};enum {Q_SIZE = 10};// private class membersNode * front; // pointer to front of QueueNode * rear; // pointer to rear of Queueint items; // current number of items in Queueconst int qsize; // maximum number of items in Queue...
public:
//...
};

在类声明中声明的结构、类或枚举被称为是被嵌套在类中,共作用城为整个类。这种声明不会创建数据对象,而只是指定了可以在类中使用的类型。如果声明是在类的私有部分进行的,则只能在这个类使用被声明的类型;如果声明是在公有部分进行的,则可以从类的外部通过作用城解析运算符使用被声明的类型。例如,如果Node是在Queue 类的公有部分声明的,则可以在类的外面声明Qucue:Node 类型的变量。

11.10.8.2 嵌套类

模板嵌套类:见模板template笔记。

11.10.8.2.1 嵌套类是什么?

嵌套类就是声明外部类的类(此处称声明嵌套类的类为外部类),它解决了不同名称空间的名称冲突的问题;
成员函数可以创建或使用嵌套类对象;在外部只能使用声明在public部分的嵌套类并且使用::操作符。

11.10.8.2.2 嵌套类与容器

嵌套类只是在外部类中声明,并不实例化;
而容器是在外部类中声明并且实例化一个对象作为外部类的成员属性。

11.10.8.2.3 嵌套类的作用域

声明在外部类public部分:外部类可以使用,外部类继承类可以使用,其他类可以使用(使用::操作符)
声明在外部类protected部分:外部类可以使用,外部类继承类可以使用,其他类不可以使用
声明在外部类private部分:外部类可以使用,外部类继承类不可以使用,其他类不可以使用

11.10.8.2.4 嵌套类的访问控制

嵌套类的public成员:外部类能访问
嵌套类的protected成员:外部类不能访问(感觉不会经常使用)
嵌套类的private成员:外部类不能访问

11.10.8.2.5 举例

queue.h

#pragma once
#include <string>
#ifndef QUEUE_H_
#define QUEUE_H_using std::string;
typedef string Item;
class Queue
{private:// class scope definitions// Node is a nested structure definition local to this classclass Node{public:Item item;Node* next;Node(const Item& i) : item(i), next(0) { }};enum { Q_SIZE = 10 };// private class membersNode* front; // pointer to front of QueueNode* rear; // pointer to rear of Queueint items; // current number of items in Queueconst int qsize; // maximum number of items in Queue// preemptive definitions to prevent public copyingQueue(const Queue& q) : qsize(0) { }Queue& operator=(const Queue& q) { return *this; }
public:Queue(int qs = Q_SIZE); // create queue with a qs limit~Queue();bool isempty() const;bool isfull() const;int queuecount() const;bool enqueue(const Item& item); // add item to endbool dequeue(Item& item); // remove item from front
};
#endif

queue.cpp

#include "queue.h"
#include <cstdlib> // (or stdlib.h) for rand()
// Queue methods
Queue::Queue(int qs) : qsize(qs)
{front = rear = NULL; // or nullptritems = 0;
}
Queue::~Queue()
{Node* temp;while (front != NULL) // while queue is not yet empty{temp = front; // save address of front itemfront = front->next;// reset pointer to next itemdelete temp; // delete former front}
}
bool Queue::isempty() const
{return items == 0;
}
bool Queue::isfull() const
{return items == qsize;
}
int Queue::queuecount() const
{return items;
}
// Add item to queue
bool Queue::enqueue(const Item& item)
{if (isfull())return false;Node* add = new Node(item); // create, initialize node// on failure, new throws std::bad_alloc exceptionitems++;if (front == NULL) // if queue is empty,front = add; // place item at frontelserear->next = add; // else place at rearrear = add; // have rear point to new nodereturn true;
}
// Place front item into item variable and remove from queue
bool Queue::dequeue(Item& item)
{if (front == NULL)return false;item = front->item; // set item to first item in queueitems--;Node* temp = front; // save location of first itemfront = front->next; // reset front to next itemdelete temp; // delete former first itemif (items == 0)rear = NULL;return true;
}

main.cpp

/*
Project name :          _16Nested_class
Last modified Date:     2022年3月28日09点39分
Last Version:           V1.0
Descriptions:           嵌套类
*/
#include<iostream>
#include"queuetp.h"
#include"queue.h"
#include<string>int main()
{using std::string;using std::cin;using std::cout;/*关于嵌套类和队列的仿真相关的文件:queue.h  and  queue.cpp*/Queue cs1(2);string temp;while (!cs1.isfull()){cout << "Please enter your name. You will be ""served in the order of arrival.\n""name: ";getline(cin, temp);cs1.enqueue(temp);}cout << "The queue is full. Processing begins!\n";while (!cs1.isempty()){cs1.dequeue(temp);cout << "Now processing " << temp << "...\n";}return 0;
}

运行结果:

Please enter your name. You will be served in the order of arrival.
name: Jasmine
Please enter your name. You will be served in the order of arrival.
name: Lily
The queue is full. Processing begins!
Now processing Jasmine...
Now processing Lily...D:\Prj\_C++Self\_16Nested_class\Debug\_16Nested_class.exe (进程 12300)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .

11.10.9 成员初始化列表的语法

如果Classy是一个类,而mem1、mem2和mem3都是这个类的数据成员,则类构造函数可以使用如下语法来初始化数据成员:

Classy::Classy(int n, int m) :mem1(n), mem2(0), mem3(n*m + 2)
{//...
}

上述代码将mem1初始化为n,将mem2 初始化为0,将mem3 初始化为n*m +2。从概念上说,这些初始化工作是在对象创建时完成的,此时还未执行括号中的任何代码。请注意以下几点:

  • 这种格式只能用于构造函数;

  • 必须用这种格式来初始化非静态const 数据成员(至少在C++11之前是这样的);

  • 必须用这种格式来初始化引用数据成员。

    数据成员被初始化的顺序与它们出现在类声明中的顺序相同,与初始化器中的排列顺序无关。
    成员初始化列表使用的括号方式也可用于常规初始化。

int games = 162;
double talk = 2.71828;
with
int games(162);
double talk(2.71828);

11.10.10 C++11类内初始化

初始化在类声明内部:

class Queue
{private:...Node * front = NULL;enum {Q_SIZE = 10};Node * rear = NULL;int items = 0;const int qsize = Q_SIZE;...
};

11.10.11 虚拟私有方法

对于一些现在不需要使用的成员函数,但是未来可能需要,可以定义为虚拟私有方法。

class Queue
{private:Queue(const Queue & q) : qsize(0) { } // preemptive definitionQueue & operator=(const Queue & q) { return *this;}//...
};

这样做有两个作用:第一,它避免了本来将自动生成的默认方法定义。第二,因为这些方法是私有的,所以不能被广泛使用。也就是说,如果nip和tuck是Queue对象,则编译器就不允许这样做:

Queue snick(nip); // not allowed
tuck = nip; // not allowed

C++类与动态内存分配相关推荐

  1. 《C++ Primer Plus》读书笔记之十—类和动态内存分配

    第12章 类和动态内存分配 1.不能在类声明中初始化静态成员变量,这是因为声明描述了如何分配内存,但并不分配内存.可以在类声明之外使用单独的语句进行初始化,这是因为静态类成员是单独存储的,而不是对象的 ...

  2. 第12章类和动态内存分配

    第12章类和动态内存分配 (1) class student {char name[40];//并不是每一个字符串都是40//如果是一个对象数组,则浪费空间 }; 12.1 (1)静态成员在类声明中声 ...

  3. C++ Primer Plus学习(十一)——类和动态内存分配

    类和动态内存分配 动态内存和类 静态类成员 特殊成员函数 string类的改进 构造函数中的new 返回对象 指向对象的指针 成员初始化列表(member initializer list) 动态内存 ...

  4. 读书笔记||类和动态内存分配

    一.动态内存和类 C++在分配内存的时候是让程序是在运行时决定内存分配,而不是在编译时再决定.C++使用new和delete运算符来动态控制内存.但是在类中使用这些运算符将导致许多新的编程问题,在这种 ...

  5. 第12章-cpp类和动态内存分配

    本章内容包括: • 对类成员使用动态内存分配. • 隐式和显式复制构造函数. • 隐式和显式重载赋值运算符. • 在构造函数中使用new所必须完成的工作. • 使用静态类成员. • 将定位new运算符 ...

  6. 类和动态内存分配——C++ Prime Plus CH12

    ①动态内存和类 1.复习示例和静态类成员 使用程序复习new和delete用法. // badstring.h文件 #include<iostream> #ifndef STRING_BA ...

  7. C++ 学习笔记之---类和动态内存分配

    参考自<C++ Primer Plus 6th Edition> 程序对内存的使用: 链接:http://zhidao.baidu.com/link?url=An7QXTHSZF7zN9r ...

  8. 第12章、类和动态内存分配

    12.1.2 特殊成员函数 C++自动提供了下面的这些成员函数: 1.默认构造函数,如果没有定义构造函数 带参数的构造函数也可以是默认构造函数,只要所有参数都有默认构造函数(只能有一个默认 构造函数, ...

  9. C++ Primer plus 第12章类和动态内存分配复习题参考答案

    假设String类有如下私有成员 class String { private:char* str; //points to string allocated by newint len; //hol ...

最新文章

  1. 深度学习相关资料总结
  2. 推荐一个非常COOL的开源相册程序!
  3. thread 在 surfaceview 中的使用与删除
  4. linux网络管理基本命令
  5. NAT网关自动去掉TCP syn包的时间戳
  6. python打开浏览器全屏_python 设置网页全屏显示
  7. php中引用的真正理解-变量引用、函数引用、对象引用
  8. MATLAB数据拟合学习总结
  9. [下载]最新QQ空间牧场开通器 V2.0
  10. 物理机安装Centos7
  11. 阿里巴巴大数据竞赛(2014年3月10日到11月)
  12. 怎么让上下两排对齐_Word文档如何让让上下两排对齐 - 卡饭网
  13. 重走来时路,这一次我会披荆斩棘
  14. CoordConv:给卷积加上坐标,从而使其具备了空间感知能力【附Pytorch实现】
  15. 这些网络流行语是什么意思!打工是不可能打工的,这辈子不可能打工的!(来自窃·格瓦拉的名言)
  16. android百度地图自动定位
  17. “打工与创业”普通人该怎么选择;唯有创业才能改变命运实现财务自由。丨国仁网络资讯
  18. 中国建筑设计公司排名
  19. 流水线“厂哥”的华丽转变!小程序带动就业超500万人
  20. 优化蓝牙低功耗设备中的功耗 伦茨科技

热门文章

  1. IEC101 遥控过程
  2. Unity游戏上传到微信小程序
  3. win10恢复默认字体
  4. 葡萄保护袋、葡萄套袋、水果保护袋、果袋
  5. 北斗授时在5G网络的应用
  6. 横幅广告(1)Admob
  7. linux普通用户转换成超级用户的好多种方法
  8. Qt编写自定义控件49-飞机仪表盘
  9. 组装硬盘录像机linux,如何自己构建一套EasyNVR这样的无插件流媒体服务器实现摄像机硬盘录像机的网页可视化直播...
  10. Bonita BPM ----- Table 控件的使用方法