通常,最好是在程序运行时(而不是编译时)确定诸如使用多少内存等问题。对于在对象中存储姓名来说,通常的C++方法是,在类构造函数中使用new运算符在程序运行时分配所需的内存。为此,通常的方法是使用string类,它将为您处理内存管理细节。下面将深入讨论动态内存分配。
C++在内存分配时采取的策略为,让程序在运行时决定内存分配,而不是在编译时决定。
在构造函数中使用new来为字符串分配空间,这避免了在类声明中预先定义字符串的长度。
静态数据成员在类声明中声明,在包含类方法的文件中初始化。初始化时使用作用域运算符来指出静态成员所属的类。但如果静态成员是整型或枚举型常量,则可以在类声明中初始化(初始化是在方法文件中,在包含类方法的文件中初始化,这是因为类声明位于头文件中,可能会多次包含该头文件,导致出现多个初始化语句副本出错。)
在构造函数中使用new来分配内存时,必须在相应的析构函数中使用delete来释放内存。如果使用new []来分配内存,则应使用delete []来释放内存。
下面这些成员函数是自动定义的
更准确地说,编译器将生成上述最后三个函数的定义—如果程序使用对象的方式要求这样做。
隐式地址运算符返回调用对象的地址(即this指针)。这与我们的初衷是一致的,在此不详细讨论该成员函数。
如果没有提供任何构造函数,C++将创建默认构造函数。
默认构造函数如下:
Klunk::Klunk() { } // implicit default constructor
就算自定义了构造函数,也需要定义一个默认构造函数:因为创建对象时总是会调用默认构造函数:
Klunk lunk; // invokes default constructor
默认构造函数使Lunk类似于一个常规的自动变量,也就是说,它的值在初始化时是未知的。
默认构造函数的特征就是没有形参,没有返回值,所以在默认构造函数内部初始化成员变量的值也是允许的:
Klunk::Klunk() // explicit default constructor
{
klunk_ct = 0;
...
}
带参数的构造函数也可以是默认构造函数,只要所有参数都有默认值。
Klunk(int n = 0) { klunk_ct = n; }
前述三个只能任选其一。
复制构造函数用于将一个对象复制到新创建的对象中。也就是说,它用于初始化过程中(包括按值传递参数),而不是常规的赋值过程中。类的复制构造函数原型通常如下:
Class_name(const Class_name &);
新建一个对象并将其初始化为同类现有对象时,复制构造函数都将会被调用。
最常见的情况是将新对象显式地初始化为现有的对象。
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 &)
每当程序生成了对象副本时,编译器都将使用复制构造函数。
具体地说,当函数按值传递对象或函数返回对象时,都将使用复制构造函数。
编译器生成临时对象时,也将使用复制构造函数。
由于按值传递对象将调用复制构造函数,因此应该按引用传递对象。这样可以节省调用构造函数的时间以及存储新对象的空间。
默认的复制构造两数逐个复制非静态成员(成员复制也称为浅复制),复制的是成员的值。
提示:如果类中包含这样的静态数据成员,即其值将在新对象被创建时发生变化,则应该提供一个显式复制构造西数来处理计数问題。
警告:如果类中包含了使用new初始化的指针成员,应当定义一个复制构造函教,以复制指向的数据,而不是指针,这被称为深度复制。复制的另一种形式(成员复制或浅复制)只是复制指针值。浅复制仅浅浅地复制指针信息,而不会深入“挖掘” 以复制指针引用的结构。
//这是复制构造函数
StringBad::StringBad(const StringBad& st)
{
cout << "Copy Contructor is Called!\n";
num_strings++; // handle static member update
len = st.len; // same length
str = new char[len + 1]; // allot space
strcpy_s(str,len+1, st.str); // copy string to new location
cout << num_strings << ": \"" << str
<< "\" object created\n"; // For Your Information
}
赋值运算符的功能即何时使用它
将已有的对象赋给另一个对象时,将使用重载的赋值运算符。
StringBad knot;
knot = headline1; // assignment operator invoked
与复制构造函数相似,赋值运算符的隐式实现也对成员进行逐个赋值。如果成员本身就是类对象,则程序将使用为这个类定义的赋值运算符来复制改成员,但静态数据成员不受影响。
可以将成员函数声明为静态,这有两个重要的效果:
静态成员函数可用于设置类范围的标志,该标志控制类接口的某些方面的行为。例如,它可以控制显示类内容的方法所使用的格式。
code:
#pragma once
// strngbad.h -- flawed string class definition
#include
using std::istream;
using std::ostream;
#ifndef STRNGBAD_H_
#define STRNGBAD_H_
class StringBad
{
private:
char* str; // pointer to string
int 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); // constructor
StringBad(); // default constructor
StringBad(const StringBad& s);//复制构造函数
~StringBad(); // destructor
int 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 -- StringBad class methods
#include // 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 size
str = new char[len + 1]; // allot storage
strcpy_s(str, len+1, s); // initialize pointer
num_strings++; // set object count
cout << num_strings << ": \"" << str
<< "\" object created\n"; // For Your Information
}
StringBad::StringBad() // default constructor
{
num_strings++; // set object count
len = 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 update
len = st.len; // same length
str = new char[len + 1]; // allot space
strcpy_s(str,len+1, st.str); // copy string to new location
cout << num_strings << ": \"" << str
<< "\" object created\n"; // For Your Information
}
StringBad::~StringBad() // necessary destructor
{
cout << "\"" << str << "\" object deleted, "; // FYI
--num_strings; // required
cout << num_strings << " left\n"; // FYI
delete[] str; // required
}
StringBad& StringBad::operator=(const StringBad& st)
{
cout << "Operator= is Called!\n";
if (this == &st) // object assigned to itself
return *this; // all done
delete[] str; // free old string
len = st.len;
str = new char[len + 1]; // get space for new string
strcpy_s(str, len + 1, st.str); // copy the string
return *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;
}
// vegnews.cpp -- using new and delete with classes
// compile with strngbad.cpp
#include
#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 value
int 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 :\n" ;
StringBad sayings[ArSize]; // array of objects
char temp[MaxLen]; // temporary string storage
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') // empty line?
break; // i not incremented
else
sayings[i] = temp; // overloaded assignment
}
int total = i; // total # of lines read
if (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";
}
else
cout << "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 left
Starting an inner block 2.
Hi, what's your name?
>> Jasmine
Jasmine, please enter up to 10 short sayings :
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。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
NULL、0还是nullptr:以前,空指针可以用0 或NULL(在很多头文件中,NULL是一个被定义为0的符号常量)来表示。C程序员通常使用NULL 而不是0,以指出这是一个指针,就像使用’\0’而不是0来表示空宇符,以指出这是一个宇符一样。然而,C++传统上更喜欢用简单的0,而不是等价的NULL。但正如前面指出的,C++11提供了关键宇nullptr,这是一种更好的选择
构造函数:
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 space
std::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 once
len = 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;
}
class Magazine
{
private:
String title;
string publisher;
...
};
String和String都使用动态内存分配。这是否意味着您需要为Magazine类编写复制构造函数和赋值操作符?不
默认的逐成员复制和赋值行为有一定的智能。如果您将一个Magazine 对象复制或赋值给另一个 Magazine 对象,逐成员复制将使用成员类型定义的复制构造两数和赋值运算符。也就是说,复制成员 title 时,将使用 String 的复制构造两数,而将成员 title 赋给另一个 Magazine对象时,将使用 String 的赋值运算符,依此类推。然市,如果 Magazinc 类因其他成员需要定义复制构造函数和赋值运算符,情况将更复杂:在这种情况下,这些两数必须显式地调用 String 和 string 的复制构造函数和赋值运算符。
如果函数返回(调用对象或参数对象)传递给他的对象,可以通过返回引用来提高效率。
以下两种实现都有效:
// version 1
Vector Max(const Vector & v1, const Vector & v2)
{
if (v1.magval() > v2.magval())
return v1;
else
return v2;
}
// version 2
const Vector & Max(const Vector & v1, const Vector & v2)
{
if (v1.magval() > v2.magval())
return v1;
else
return v2;
}
有三个要点:
两种常见的返回非const对象的情形是,重载赋值运算符以及重载与cout一起使用的<<运算符。
operator=()的返回值用于连续赋值:
String s1("Good stuff");
String s2, s3;
s3 = s2 = s1;
在上述代码中,s2.operator=()的返回值被赋给s3。为此,返回 String 对象或 String 对象的引用都是可行的,但与 Vector 示例中一样,通过使用引用,可避免该两数调用 String 的复制构造两数来创建一个新的String 对象。在这个例子中,返回类型不是const,因为方法 operator=0返回一个指向s2 的引用,可以对其进行修收。
operator<<()的返回值是为了串联输出:
String s1("Good stuff");
cout << s1 << "is coming!";
在上述代码中,operator<< (cout,s1)的返回值成为一个用于显示字符串“is coming!” 的对象。返回类型必须是 ostream &,而不能仅仅是ostream。如果使用返回类型ostream,将要求调用 ostream 类的复制构造函数,而ostream 没有公有的复制构造两数。幸运的是,返回一个指向 cout 的引用不会带来任何问题,因为 cout 已经在调用两数的作用域内。
如果被返回的对象是被调用两数中的局部变量,则不应按引用方式返回它,因为在被调用两数执行完华时,局部对象将调用其析构函数。因此,当控制权回到调用两数时,引用指向的对象将不再存在。在这种情况下,应返回对象而不是引用。通常,被重载的算术运算符属于这一类。
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类)的对象,它必须返回一个指向这种对象的引用。
正常情况下是这样使用的:
net = force1 + force2;
但是呢,程序员在编写程序的过程中可能会出现下面这样的语句,这是不应该允许的,因为可能会运行出错:
force1 + force2 = net;
cout << (force1 + force2 = net).magval() << endl;
因此:
r如果您担心这种行为可能引发的误用和滥用,有一种简单的解决方案:将返回类型声明为const Vector。
Code:
// placenew1.cpp -- new, placement new, no delete
#include
#include
#include
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 memory
JustTesting *pc1, *pc2;
pc1 = new (buffer) JustTesting; // place object in buffer
pc2 = new JustTesting("Heap1", 20); // place object on heap
cout << "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 Heap1
delete pc4; // free Heap2
delete [] buffer; // free buffer
cout << "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
#include
#include
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 memory
JustTesting *pc1, *pc2;
pc1 = new (buffer) JustTesting; // place object in buffer
pc2 = new JustTesting("Heap1", 20); // place object on heap
cout << "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 location
pc3 = 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 Heap1
delete pc4; // free Heap2
// explicitly destroy placement new objects
pc3->~JustTesting(); // destroy object pointed to by pc3
pc1->~JustTesting(); // destroy object pointed to by pc1
delete [] buffer; // free buffer
cout << "Done\n";
return 0;
}
C++允许将结构体、类和枚举定义在类声明中。
class Queue
{
private:
// class scope definitions
// Node is a nested structure definition local to this class
struct Node { Item item; struct Node * next;};
enum {Q_SIZE = 10};
// private class members
Node * front; // pointer to front of Queue
Node * rear; // pointer to rear of Queue
int items; // current number of items in Queue
const int qsize; // maximum number of items in Queue
...
public:
//...
};
在类声明中声明的结构、类或枚举被称为是被嵌套在类中,共作用城为整个类。这种声明不会创建数据对象,而只是指定了可以在类中使用的类型。如果声明是在类的私有部分进行的,则只能在这个类使用被声明的类型;如果声明是在公有部分进行的,则可以从类的外部通过作用城解析运算符使用被声明的类型。例如,如果Node是在Queue 类的公有部分声明的,则可以在类的外面声明Qucue:Node 类型的变量。
模板嵌套类:见模板template笔记。
嵌套类就是声明外部类的类(此处称声明嵌套类的类为外部类),它解决了不同名称空间的名称冲突的问题;
成员函数可以创建或使用嵌套类对象;在外部只能使用声明在public部分的嵌套类并且使用::操作符。
嵌套类只是在外部类中声明,并不实例化;
而容器是在外部类中声明并且实例化一个对象作为外部类的成员属性。
声明在外部类public部分:外部类可以使用,外部类继承类可以使用,其他类可以使用(使用::操作符)
声明在外部类protected部分:外部类可以使用,外部类继承类可以使用,其他类不可以使用
声明在外部类private部分:外部类可以使用,外部类继承类不可以使用,其他类不可以使用
嵌套类的public成员:外部类能访问
嵌套类的protected成员:外部类不能访问(感觉不会经常使用)
嵌套类的private成员:外部类不能访问
queue.h
#pragma once
#include
#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 class
class Node
{
public:
Item item;
Node* next;
Node(const Item& i) : item(i), next(0) { }
};
enum { Q_SIZE = 10 };
// private class members
Node* front; // pointer to front of Queue
Node* rear; // pointer to rear of Queue
int items; // current number of items in Queue
const int qsize; // maximum number of items in Queue
// preemptive definitions to prevent public copying
Queue(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 end
bool dequeue(Item& item); // remove item from front
};
#endif
queue.cpp
#include "queue.h"
#include // (or stdlib.h) for rand()
// Queue methods
Queue::Queue(int qs) : qsize(qs)
{
front = rear = NULL; // or nullptr
items = 0;
}
Queue::~Queue()
{
Node* temp;
while (front != NULL) // while queue is not yet empty
{
temp = front; // save address of front item
front = front->next;// reset pointer to next item
delete 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 exception
items++;
if (front == NULL) // if queue is empty,
front = add; // place item at front
else
rear->next = add; // else place at rear
rear = add; // have rear point to new node
return 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 queue
items--;
Node* temp = front; // save location of first item
front = front->next; // reset front to next item
delete temp; // delete former first item
if (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
#include"queuetp.h"
#include"queue.h"
#include
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。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
如果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);
初始化在类声明内部:
class Queue
{
private:
...
Node * front = NULL;
enum {Q_SIZE = 10};
Node * rear = NULL;
int items = 0;
const int qsize = Q_SIZE;
...
};
对于一些现在不需要使用的成员函数,但是未来可能需要,可以定义为虚拟私有方法。
class Queue
{
private:
Queue(const Queue & q) : qsize(0) { } // preemptive definition
Queue & operator=(const Queue & q) { return *this;}
//...
};
这样做有两个作用:第一,它避免了本来将自动生成的默认方法定义。第二,因为这些方法是私有的,所以不能被广泛使用。也就是说,如果nip和tuck是Queue对象,则编译器就不允许这样做:
Queue snick(nip); // not allowed
tuck = nip; // not allowed