构造函数和析构函数
构造函数
1.构造函数的定义
类的构造函数(constructor)是类中的一个非常特殊的成员,它甚至没有名字
C++用了函数修饰符(function specifier)来说明构造函数的存在。
构造函数不仅在声明形式上非常特别,它的“返回值”也很特殊:在声明或定义构造函数时不能为其指定返回类型,甚至void也不行。
函数修饰符的语法为:
函数名(参数列表);
构造函数定义的语法如下:
class 类名
{
public:
类名(参数列表); //构造函数声明
};
2.构造函数的作用
• 创建类的一个新对象时,编译器将在创建的地方自动生成调用构造函数的代码,用以完成对象的初始化工作。
• 类的构造函数的作用是:
– (1) 分配一个对象的数据成员的存储空间;(该功能由系统自动完成。)
– (2) 执行构造函数(体),一般是初始化一个对象的部分或全体数据成员。
3.构造函数的特征
类的构造函数有如下特征:
• 构造函数不能是虚函数;
• 构造函数不能是静态的;
• 构造函数不能是const的;(思考:why?)
• 不能获取构造函数的地址;
• 构造函数因其没有函数名而不能被显式调用。
但在显式类型转换时,可以使用函数标记法(functional notation)来引起构造函数的调用。函数标记法的语法为:
类名(参数列表);
class array
{
public:
array()
{
capacity = c;
head = c?new int[c]:nullptr;
len = 0;
}
}
一旦为类显式定义了一个构造函数,那么每当在程序中用如下方式定义对象时,例如:
array a;
a对象的构造函数在该对象定义的同时会被自动调用,从而构造出一个具有初始状态的对象。这是一种编译器强制行为。
4.构造函数的访问控制属性
在绝大多数情况下,类的构造函数应该具有public访问属性
特例:在需要禁止复制的情况下,类的(某种)构造函数就应该是私有的
5. 构造函数的参数
array::array(size_t c) //完全取代create()的功能
{
capacity = c;
head = new int[c];
len = 0;
}
array a(5); //ok
array b; //error!
注:构造函数的参数是可以缺省的
• 构造函数参数可以是缺省的,例如:
array::array(size_t c = 16) { … }
• 在定义对象时,就可以不必指定参数。例如:
array c; //OK
array d(1024); //OK
6.初始化列表
构造函数的初始化列表是一种特殊的初始化机制,它的语法如下:
构造函数名(参数列表) [: 成员名(表达式) [, 成员名(表达式)…]]
array(size_t c = 16) : capacity(c), len(0), head(c ? new int[c] : nullptr) {}
• 初始化列表的执行先于构造函数体的执行
• 初始化列表中的成员必须是类的直接成员
• 常量、独立引用等特殊成员,初始化只能在初始化列表中进行
7.隐式默认构造函数
编译器自动给该类类型合成一个没有参数的构造函数,并且该函数的函数体没有任何语句
当建立一个对象时,这个隐式缺省构造函数会被自动调用
例外
一旦一个类有一个显式定义的构造函数,编译器不会自动合成隐式默认构造函数
采用初始化列表方式,必须使用显式定义的构造函数
8. 显式构造函数声明
类的构造函数可以被关键字explicit修饰,说明成是“显式的”,例如:
class array { public: explicit array() {...} };
一旦类的构造函数被声明为是显式的,那么一些隐式的类型转换将被阻止。
重载构造函数
1.构造函数的重载
一个类可以提供多个构造函数,用于在不同场合进行类对象的初始化工作。很明显,这是构造函数的重载,它们的参数表必须互不相同。
提问:如果一个类拥有默认构造函数,那么它还可以有一个所有参数都可缺省的构造函数吗?(答:不能——思why)
array::array() : capacity(0), len(0), head(nullptr) {}
array::array(size_t c) : capacity(c), len(0), head(c ? new int[c] : nullptr) {}
array::array(size_t c, int* d) : capacity(c), len(0), head(new int[c])
{ for (size_t i = 0; i < c; ++i) push_back(d[i]); }
array a; //call array()
array b(10); //call array(size_t)
int data[] = {1, 3, 5, 7};
array c(4, data); //call array(size_t, int*)
2.构造函数委托
可以使用构造函数委托(constructor delegates)来简化类的设计
array::array() : array(0) /*委托至array(size_t)*/ {}
array::array(size_t c) : capacity(c), len(0), head(c ? new int[c] : nullptr) {}
array::array(size_t c, int* d) : array(c) /*委托至array(size_t)*/
{ for (size_t i = 0; i < c; ++i) push_back(d[i]); }
析构函数
1.析构函数
类的析构函数也是一种特殊成员,没有函数名。如果一定要给它命名,那么它的名字可以看做是在类名前加“~”,然后跟上一对空的圆括号(即没有参数)构成的,同时不能为其指定返回类型。
void array::empty() { delete[] head; len = 0; head = nullptr; }
array::~array() { empty(); } //destructor
2.析构函数的特征
• 析构函数不能有参数,因此不能被重载;
• 不能获取析构函数的地址;
• 析构函数可以被显式调用,例如:r.~Rectangle()。一旦析构函数被显式调用,那么类对象将会失效;
• 析构函数可以是虚(virtual) 或是纯虚(pure virtual) 的,并且也应该是虚的。
析构函数的作用是:
- 执行析构函数(一般没有具体的工作);
- 释放对象的存储空间。(该功能由系统自动完成。)
- 释放对象占用的资源。这项工作要由程序员设定。
class Foo
{
private:
char *memblock;
public:
Foo(size_t size){memblock = new char[size];}
~Foo(){delete []memblock;}
};
class Test
{
int id;
public:
Test(int i)
{
id = i;
}
~Test()
{
cout<<"ID: "<<id<<" destruction function is invoked!"<<endl;
};
};
int main()
{
Test t0(0); //栈中分配
Test t1[3]{1,1,1}; //栈中分配,数组型对象
Test *t2 = new Test(2); //堆中分配
delete t2;
Test *t3 = new Test[3]{3,3,3};//堆中分配
delete []t3;
cout<<"------End of Main-------"<<endl;
return 0;
}
分析:
在main函数中创建了t0,t1,t2,t3几个对象,这里先说一下C++创建对象的三种不同方式:
1、Test p1(1); //栈中分配内存
2、Test p2 = Test(2); //栈中分配内存,跟方法1相同,是方法1的完整模式
3、Test *p3 = new Test(3); //堆中分配内存
方法1、2中都是在栈中分配内存,在栈中内存由系统自动的去分配和释放,而使用new创建的指针对象是在堆中分配内存,当不需要该对象时,需要我们手动的去释放,否则会造成内存泄漏。
在上述程序中,t0和t1都是栈中的对象,在程序结束时由系统来释放,因此出现在“----End of Main”之后。
t2,t3是new出来的堆中对象,所以需要手动的delete释放,因此出现在最前面。
另外有一点发现,就是栈中对象的释放顺序,是后定义的先释放,经过几次验证也如此,我想这恰好应征了栈的后进先出的特征。
class Test
{
int id;
public:
Test(int i)
{
id = i;
}
~Test()
{
cout<<"ID: "<<id<<" destruction function is invoked!"<<endl;
};
};
Test t0(0); //最先创建的对象,最后释放
void Func()
{
static Test t1(1); //创建静态对象,会在整个程序结束时自动释放
Test t2(2); //在Func结束时自动释放
cout<<"-----Func-----"<<endl;
}
int main()
{
Test t3(3);
t3 = 10; //类型转换构造函数,这里会创建临时对象,将int型转成Test类型对象,在赋值结束后,临时变量销毁
cout<<"------Begin of Main-------"<<endl;
{
Test t4(4); //花括号代表作用域,不需要等到main方法结束就释放了
}
Func(); //进入Func函数
cout<<"------End of Main-------"<<endl;
return 0;
}
思考:为什么C++规定不能获取析构函数和构造函数的地址?
https://www.cnblogs.com/findumars/p/3746869.html
思考:析构函数,delete,delete[]的关系?
https://blog.csdn.net/qq_23363993/article/details/52211482
复制构造函数
什么是对象的复制?
复制的两种情况:
• 赋值
在程序中,往往需要进行两个类对象之间的复制,使它们的内部状态相同
• 创建对象时复制
在创建对象时就可以使用已存在的对象对新对象进行复制
参与复制操作的构造函数就是“复制构造函数(copy constructor) ”
注:没有复制构造函数的类
#include<iostream>
class array {
public:
array() { std::cout << "array():counter=" << ++counter << std::endl; }
~array() { std::cout << "~array():counter=" << --counter << std::endl; }
static int counter;
};
int array::counter = 0;
array f(array t)
{
std::cout << "f():counter=" << array::counter << std::endl;
return t;
}
int main() {
array a;
f(a);
return 0;
}
仔细分析输出结果,可以得到以下结论:
• 一定产生了三个对象,因为有三条析构函数产生的信息;
• 在进入函数f()的时候结果:此时已经存在两个对象(t和a),然而counter的值却只是1;
• f()返回时产生了的临时对象x,但看起来x的构造函数似乎也没有被调用。
• 唯一合理的解释就是:编译器为类合成了一个构造函数,而它与显式定义的那个版本是不一样的。
合成构造函数的调用形式:
rect.Rectangle(r);
显然,编译器无法洞悉程序员的设计意图,因此合成版本只能采用了一种简单做法完成复制:逐成员复制。
逐成员复制的规则显然不适用于静态数据成员。
解决方案:为类显式提供一个用于复制的构造函数。
1. 复制构造函数的定义
复制构造函数的形式化定义为:
class 类名
{
public:
类名(const 类名&[, other parameters]); //copy constructor
};
提问:为什么参数必须是引用?
避免陷入递归死循环调用
Rectangle::Rectangle(const Rectangle& r) : name(r.name), width(r.width), height(r.height)
{
++Quadrangle::counter ;
cout<<“in Rectangle”<<Quadrangle::counter<<endl;
}
何处会调用复制构造函数
• 显式定义复制对象时,如下例所示:
– array a1;
– array a2(a1); //调用a2的复制构造函数
– array a3 = a2; //这不是对象间的赋值,而是复制(初始化)!
• 实参和形参结合时。如例中t和a结合时,将会调用形参t的复制构造函数来复制实参对象r;
• 函数返回值对象(非指针和引用)时。如例中f()返回一个临时对象,这个临时对象就是用其复制构造函数从return的返回表达式t中复制而来。这个临时对象是匿名的,并且被视为常量对象。
2. 浅复制和深复制
请先阅读程序。
array(const array& a) : capacity(a.capacity), len(a.len), head(a.head) {}
这种仅将类对象本身占据的内存地址复制到另一个对象中的过程称为“浅复制”。
浅复制可能带来潜在的危险。假设有如下代码:
int main()
{
int d[] = {5, 6, 7, 8};
array a(4, d), b(a);
b.traverse(print);
b.~array();
a.traverse(print);
return 0;
}
提问:
- 哪个对象首先被析构?
- 此后会发生什么情况?
对象b首先被析构。在我们的设计中,析构函数将释放掉存储空间。这项工作是很有意义的。然而,这项有意义的事却在上述语境中变成了灾难:在随后调用的对象a的析构函数中,试图释放与b共享的同一个存储,然而这个存储空间早就已经不存在了!
解决方案:将链表整体复制到目标对象。这就是“深复制”的思想,即在目标对象中为源对象的所有资源制作一个副本。
array::array(const array& a) : capacity(a.capacity), len(0), head(new int[a.capacity])
{
for (size_t i = 0; i < a.len; ++i)
push_back(a.head[i]);
}
注:可以在构造函数中调用其他确定的成员函数。
另:禁止复制
在某些特别的应用中,我们编写的List类可能只会产生一个实例。这样一来,List类的复制构造函数实际上是没有用处的。在这种情况下,应该考虑禁止复制的发生。
也许会想到这样的方法:不为List类提供复制构造函数。
提问:
- 这样做行得通吗?
编译器会为类合成一个隐式默认的复制构造函数。 - 那么将复制构造函数放到private段中如何?
虽然常规途径的复制尝试会被编译器拒绝,但该类的友元可以引起私有复制构造函数的合法调用。
一种做法就是在private段中声明一个复制构造函数,但不给出定义。例如:
class array
{
private:
array(const array&); //declaretion only
//other members
};
只声明而不定义会“骗过”编译器。但如果不小心引起了这种仅有声明的私有复制构造函数的调用,将会产生一个链接错误。
更好的禁止复制的做法是采用C++ 1y的语法,将复制构造函数从类中“删除”,具体的语法是:
class array
{
private:
array(const array&) = delete;
//other members
};
3. 转移复制构造函数
假设有如下代码:
array a1(10), a2(a1);
这种复制过程可以概括为三步:
第一步:为复制目标申请资源;
第二步:将资源内容从复制源复制到目标资源中;
第三步:释放复制源的资源,此后复制源失效。
如果复制源a1是临时对象,意味着在复制时,它即将失效(expiring);复制完成后它会立即失效,所占据的资源也将被释放。
问题:既然复制源最终要失效,也就是说,它的资源以后不会再用了,那么何不省掉复制操作,而直接将资源转移(move)给复制目标呢?
再谈对象的创建和初始化
1.对象的创建和释放
1)全局对象
全局对象拥有与程序运行期等长的生命期:当程序启动时,全局对象被构建;程序结束时,全局对象被析构。
2)局部对象
void f(bool b)
{
array a1, a2; //a1和a2的生命期到f函数返回
if (b) { array a3; } //a3的生命期到if语句结束
array a4; //a4的生命期到f函数返回
}
若调用函数f,则调用构造函数的顺序是:a1、a2、a3、a4;调用析构函数的顺序是:a3、a4、a2、a1。
3)匿名对象
设有如下代码:
array g() { return array(); }
在函数g()的return语句中,函数标记法引起了类的构造函数的调用,从而产生一个匿名的临时对象。在正常情况下,编译器将这个匿名临时对象视为常量,因此它只能作为右值对待。
4)动态对象
自由对象通过使用new运算符来创建的。产生的对象拥有长至整个程序运行期的生命周期。
当该对象完成使命后,应该立即使用delete释放来对象(实际上调用析构函数)。
void k()
{
array *p = new array(); //调用构造函数
delete p; //调用析构函数
}
如果没有显式释放自由创建的对象,那么该对象将不会自动消失,因为C++没有自动垃圾回收机制;而且,自由对象往往会因为没有别的指针指向而再也无法访问,成为孤悬的对象。
不能使用C风格的malloc()和free()来创建和释放对象,因为这两个函数不会引起构造函数和析构函数的调用。
2.对象的初始化-常规
int a = 1;
double b(2.0);
int c[] = { 1, 2, 3, 4 };
char *p = new char('A'); //分配存储的同时完成初始化
struct A
{
int x;
struct B
{
int i;
int j;
} b;
} m = { 1, { 2, 3 } }; //1赋给了m.x,2赋给了m.i,3赋给了m.j
array a(10); //调用构造函数完成初始化
array b(a); //调用复制构造函数完成初始化
3.对象的初始化-统一初始化
int a = {1}; //等价于 int a = 1
int b{2}; //等价于 int b = 2
int *p = new int[4]{1, 2, 3, 4}; //数组p的每个元素都被初始化
struct X { int a, b; } x{0, 1}; //x.a = 0, x.b = 1
X f(X o) { return {1 + o.a, 2 + o.b}; }
auto e = f({3, 4}); //e的类型是X,其值是{4, 6}
X n; //n未初始化
n = { 7, 8 }; //赋值
class Y
{
public:
int c; double d;
Y(int x, double y) : c(x), d(y) {}
} m{5, 6.0}; //m.c = 5,m.d = 6.0
对象和指针
1.this指针
问题:成员函数如何知道自己是被哪个对象调用呢?
this指针
this是一个C++关键字
隐含存在于任一个非静态成员函数中,不能被显式声明
设有如下对象定义:
Rectangle rect;
那么,对象rect所有非静态成员函数里的this指针可以形象地表示为(但不能显式声明):
Rectangle * const this = ▭
成员函数
double Rectangle::area()
{
return width * height;
};
将被编译器改造为:
double Rectangle::area(Rectangle * const this)
{
return this->width * this->height;
};
this指针只能出现在类的非静态成员函数中,并且常用于需要自引用的地方。
Rectangle& Rectangle::me()
{
return *this;
};
类的静态成员函数没有this指针。这符合“静态成员属于类而不属于对象”的规则。
2.指向类对象的指针
void f()
{
array a;
array *pa = &a; //指针指向一个编译器对象
pa->traverse(print);
//delete pa; //error,不能用这种方式来释放编译器对象
pa->~array(); //但可以显式调用这个对象的析构函数:
pa = new array();
pa->traverse(print);
delete pa; //OK
}
3.指向类成员的指针
- 类成员指针的定义
指向类成员的指针不属于类,它们定义在类的外部,其语法为:
类型名 类名::*指针;
类型名 (类名::*指针)(参数表);
class X
{
private:
int a;
public:
int b;
float c;
int f();
int g();
int h(int);
};
如有定义
int X::*ptr;
int (X::*fptr)();
那么以上两个指针可以指向类X的那些成员?
ptr可以指向b,但不能指向a(因其私有)和c(类型不同);
fptr可以指向f()或g(),但不能指向h()(类型不同)。
- 类成员指针的使用
在使用类成员指针之前,必须对其进行初始化。给指向类成员指针的初始化工作可以发生在定义类对象之前。下面的代码完成了指针与类成员的绑定:
ptr = &X::b;
fptr = &X::f;
由于没有对象产生,因此ptr和fptr将不知道自己作用在哪个对象上,因而这种初始化工作只是形式上的关系确定。要使指针发挥作用,必须定义对象,然后使用成员选择运算符.*或->*来完成操作。
void fun()
{
X Obj;
X *pObj = &Obj;
ptr = &X::b;
fptr = &X::f;
Obj.*ptr = 2; //Obj.b = 2
++ pObj->*ptr; // ++Obj.b
(Obj.*fptr)(); //call Obj.f();
(pObj->*fptr)();
fptr = &X::g;
(Obj.*fptr)(); //call Obj.g()
}
代码
Obj.fptr()
是错误的。因为这会首先解释为
Obj.(fptr())
这就意味着,fptr是个函数,并将它的返回值绑定到成员选择运算符.*上,这显然是不正确的。
友元关系
1.友元函数
一个对象的私有数据,只能通过成员函数进行访问,这是一堵不透明的墙。这种限制性给这样一种情况造成了困扰:类的某些成员原则上应该是私有的,但却需要在外部频繁的访问他们
故引入友元(friend)机制
一个类的友元可以是一个外部函数,也可以是一个类。它们虽然不是该类的成员,但却能访问该类的任何成员。这显然提高了访问效率
友元分类:1.友元函数 2.友元类
• 语法:
class 类名
{
//other members;
friend void fun();
friend void class B::fun();
friend class B;
};
• 例如:
int f();
class A { friend int f(); }
友元声明必须放在类定义中,但放在哪个段中无关紧要。
在友元函数中直接访问类的私有成员
class Rectangle //简化版
{
private:
int width, height;
//other members
public:
friend int perimeter(const Rectangle& r);
};
int perimeter(const Rectangle& r)
{
return (r.width + r.height) * 2;
}
一旦声明了类的友元,那么该类的作用域就对友元开放。也就是说,类的所有成员对友元都是可见的、可访问的
友元的作用范围仅限在直接声明它的类中。友元不能逾越嵌套类的界限而访问到外部类,除非友元同时也被显式声明为外部类的友元
例如:
class C
{
friend int f();
};
class A
{
class B { friend int f(); }
C objC;
};
函数f()仅仅是类B和类C的友元,而非类A的友元。
class Rectangle; //forward declaration
class Calculator
{
public:
int perimeter(const Rectangle& r);
};
class Rectangle //简化版
{
private:
int width, height;
//other members
public:
friend int Calculator::perimeter(const Rectangle& r);
};
int Calculator::perimeter(const Rectangle& r)
{
return (r.width + r.height) * 2;
}
2.友元类
如果将一个类A声明为类B的友元类,那么,类A的所有成员函数都成为类B的友元函数。
class Painter;
class Rectangle //简化版
{
private:
int width, height;
//other members
public:
friend class Painter;
};
class Painter
{
public:
void draw(const Rectangle& r)
{
cout << r.width << ',' << r.height << endl;
}
};
3.友元关系的特性
友元具有如下的特性:
• 非传递性。即A是B的友元,B是C的友元,但A不一定是C的友元(除非将A声明为C的友元);
• 非对称性。即A是B的友元,但B不一定是A的友元(除非将B声明为A的友元)。
与类和对象相关的问题
1.对象数组
对象数组的每个数组元素都是一个对象
需要多次调用构造函数
释放对象数组时,也需要多次调用析构函数例如:
array a[10];
要创建一个类的对象数组,该类的构造函数必须满足下列几个条件之一:
• 没有显式定义的构造函数;
• 有显式定义的构造函数,但其中有一个构造函数没有参数;
• 有显式定义的构造函数,但其中有一个构造函数的所有参数都可以默认;
除了直接定义对象数组外,还可以使用new运算符来动态创建对象数组。例如:
array *p = new array[3];
而在使用完毕后,可以使用delete运算符来释放整个数组。例如:
delete []p;
2.对象作为函数参数
void f(Rectangle r); //值参数
void g(Recangle *r); //指针参数
void h(Rectangle &r); //引用参数
• 对象的值做参数,对形参对象的任何修改都不影响用作实参对象。
• 对象引用做参数,对形参对象的任何修改就是直接对实参对象的修改。一般情况下,选择常量引用作为参数是一种非常好的选择。
• 对象指针做参数,对它指向的对象作任何修改就是间接对实参对象的修改;而修改参数本身将会导致参数指针指向别的对象,对实参对象没有任何影响。
3. 函数返回对象
Rectangle f(Rectangle r) { return r; } //返回值
Rectangle* g(Recangle *r) { return r; } //返回指针
Rectangle& h(Rectangle& r) { return r; } //返回引用
• 函数f()返回对象r的值,这要产生一个匿名临时常量对象
• g()返回对象的指针,也就是返回对象的地址,不会引起构造函数的调用
• h()返回对象的引用,就是返回对象本身,可以作为左值使用。
• 需要注意的是,在函数返回对象指针或引用时,被指向或被引用的对象必须具有超出函数作用域的生命期。
• 例
– 函数:Rectangle& f(Rectangle r) {return r;}会出现什么问题?
答:临时变量是有生命周期的,即在被调用函数内该临时变量才会存在,当函数结束并准备返回参数时,该临时变量已经不存在,这种情况下返回它的引用是不允许的。
4.常量对象
const关键字可以约束普通变量,也可以约束一个对象,使之成为常量对象。例如:
const array a;
这样一来,对象rect的所有属性都是不可修改的,除非某个属性被说明成是mutable
常量对象的两种使用情形:
• 函数返回对象的值这一情况。这个返回的对象被编译器自动约束成为常量对象
• 常量对象作为函数的参数
与无约束对象一样,可以调用常量对象的成员函数来完成某项操作。但这可能带来潜在的错误:这个成员函数可能会修改对象的属性
5.常成员函数
类的某些成员函数只是读取属性而不修改它们。这样,可以将这样的成员说明成是常成员。例如:
size_t array::size() const { return len; }
关键字const将成员函数area()的this指针和它指向的对象约束成为常量,因此在其内部任何试图改变对象状态的操作都是非法的。在常成员函数中不能调用非常成员函数,因为那些函数有可能改变对象的状态。
如果在类内声明常成员函数而在类外定义它,那么二者的声明必须完全一致。例如:
class array //简化版
{
public:
double area() const;
};
double array::area() const
{ return …; }
6.类中的类型
我们在设计类时会遇到这样的问题:类B需要另一个类A提供服务。如果类A只为类B提供服务,那么A最好成为B的内部类。这里,类A称为类B的“嵌套类(nested class)”,而类B是类A的“包围类”。
- 类中的类类型
class encircle
{
public:
int i;
class nested
{
public:
void f();
};
};
嵌套类的名字完全局部于它的包围类,而它的作用域也被局限在包围类的作用域中。这样一来,嵌套类对包围类以外来说是不可见
的,因此在包围类外直接使用嵌套类的名字是不合法的。下面的代码是错误的:
void g()
{
nested n; //error
}
如果一定要在包围类外使用嵌套类的名字,或者定义嵌套类的成员,那么必须使用名字限定。例如:
void g()
{
encircle::nested n; //OK
}
请注意这样一个事实:nest定义在encircle的public段中。这使得上述使用方法是正确的。否则,那么它的名字也被私有化了,因此在在包围类外就无法访问嵌套了。
嵌套类形成了一个局部作用域,包围类的成员在这个作用域中是不可见的。下面的f()定义是错误的:
void encircle::nested::f()
{
i = 0; //error,包围类的成员对嵌套类是不可见的
encircle::i = 0; //error,i不是encircle类的静态成员
}
反过来,嵌套类的作用域对包围类来说也是封闭的。
嵌套类可以被声明为包围类的友元。这样,嵌套类可以通过包围类的对象直接访问其所有成员。例如:
class encircle
{
private:
int i;
friend class nested; //嵌套类成为包围类的友元
class nested
{
public:
void g(encircle& e) { e.i = 0; } //OK
};
};
7. 类中的枚举类型
在类中定义枚举类型的语法形式与在类中定义类非常类似。例如:
class encircle
{
public:
enum STATUS { WRONG, RIGHT };
…
};
这样,类型STATUS被限制在包围类encircle的作用域中。
• 类中的枚举成员不属于对象,而是该类的所有对象共享的。因此,对枚举成员的访问必须采用名字限定的方式进行。例如:
encircle::STATUS s = encircle::RIGHT; //OK
s = WRONG; //error
• 同样地,如果枚举定义被放在非公有段中,以上访问如果发生在类之外就是非法的。
8. 类中的typedef
可以在类中用typedef为已有的类型取一个别名。例如:
class encircle
{
public:
typedef int* _range;
};
与类中的其它名字一样,别名_range也被局限在包围类的作用域中。同样地,在包围类外使用它必须使用名字限定。例如:
encircle::_range p; //OK
_range q; //error