类的作用域
同变量,函数一样,类也有自己的作用域
(1)每个类都定义了自己的作用域和唯一的类型。在类体内声明类成员,将成员名引入类的作用域中。两个不同的类具有两个独立的类作用域。即使两个类具有完全相同的成员列表,它们也是不同的类型。每个类的成员不同于任何其他类的成员。
例如:
class A { public: int i;double d; };
class B { public:int i;double d; };
class Data { public: int member;void memfunc(); };
A obj1;
B obj2 = obj1; //错误,不同的类类型对象不能赋值
(2)在类作用域之外,成员只能通过对象,指针或引用的方式(使用成员访问操作符".“或”-")来访问。这些运算符左边的运算对象分别是一个类对象,指向类对象的指针或对象的引用,后面的成员名字必须在相对应的类的作用域中声明。
int a;
Data obj; //定义对象
Data *ptr=&obj,&r=obj; //定义指针和引用
a = member; //错误,member需要作用域限定
obj.member; //正确,通过对象使用member
a = ptr->member; //正确。通过指针使用member
r.memfunc(); //正确,通过引用使用memfunc
(3)静态成员,类中定义的类型成员需要直接通过类作用域运算符"::"来访问
class Data {
public:
enum COLORS {RED,GREEN,BLUE,BLACK,WHITE}; //声明枚举类型
COLORS getcolor();
};
COLORS c1; //错误,COLORS只能在类作用域中
Data::COLORS cc; //正确,Data::限定COLORS在类作用域中
(4)定义于类外部的成员函数的形参列表和函数体也都是在类作用域中,所以可以直接引用类的其他成员。例如:
方框内都是Data类的作用域。
- 类作用域中的名字查找
名字查找(name lookup),即寻找与给定的名字相匹配的声明的过程。
(1)首先,在使用该名字的块中查找名字的声明,且只考虑在该名字使用之前声明的名字
(2)如果找不到该名字,则在包围的作用域中查找
(3)如果找不到任何声明,则编译错误
比较容易引起混淆的是在类体内定义的名字
(1)类成员声明的名字查找
按以下方式确定在类成员的声明中用到的名字:
1.检查出现在名字使用之前的类成员的声明
2.如果步骤一查找不成功,则检查包含类定义的作用域中出现的声明以及出现在类定义之前的声明
必须在类中先定义类型名字,才能将它们用作数据成员的类型,或者成员函数的返回类型或形参类型。编译器按照成员声明在类中出现次序来处理名字
(2)类成员定义中的名字查找
按以下方式确定在成员函数的函数体中用到的名字
1.首先检查成员函数局部作用域中的声明
2.如果在成员函数中找不到该名字的声明,则检查所有类成员的声明
3.如果在类中找不到该名字的声明,则检查在此成员函数定义之前的作用域中出现的声明
- 嵌套类
可以在类A的内部定义类B,称为类B为嵌套类(nested class),又称为嵌套类型(nested type),称类A为外围类(enclosing class)。
例如:
class Queue { //外围类定义体
private:
struct QueueItem { //嵌套类
QueueItem (const int &);
int item;
QueueItem *next;
};
QueueItem *head,*tail;
};
说明:
(1)嵌套类是独立的类,基本上与它们的外围类不相关,因此,外围类和嵌套类的对象是相互独立的。嵌套类型的对象不包含外围类所定义的成员,同样,外围类的成员也不包含嵌套类所定义的成员。
(2)嵌套类的名字只在其外围类的作用域中可见,嵌套类的名字不会与另一作用域中声明的名字冲突。
(3)嵌套类可以具有与非嵌套类相同种类的成员。像任何其他类一样,嵌套类可以使用访问标号控制对自己成员的访问。成员可以声明为public,private或protected。外围类对嵌套类的成员没有特殊访问权,并且嵌套类对其外围类的成员也没有特殊访问权。
(4)在外围类的public部分定义的嵌套类定义了可在任何地方使用的类型,在外围类的protected部分定义的嵌套类定义了只能由外围类,友元或派生类访问的类型,在外围类的private部分定义的嵌套类定义了只能被外围类或其友元访问的类型。
嵌套类可以直接引用外围类的静态成员,类型名和枚举成员。当然,引用外围类作用域之外的类型名或静态成员,需要作用域运算符(::)。
- 局部类
可以在函数体内部定义类,这样的类称为局部类。一个局部类定义了一个类型,该类型只在定义它的局部作用域中可见
(1)局部类的所有成员(包括函数)必须完全定义在类体内
(2)局部类可以访问外围作用域中的名字是有限的。局部类只能访问在外围作用域中定义的类型名,静态变量和枚举成员,不能使用定义该类的函数中的变量
(3)外围函数对局部类的私有成员没有特殊访问权,当然,局部类可以将外围函数设为友元。实际上,局部类中private成员几乎是不必要的,通常局部类的所有成员都为public成员
(4)可以访问局部类的程序部分是非常有限的。局部类封装在它的局部作用域中,进一步通过信息隐藏进行封装是不必要的
int a,v;
void fun(int v) {
static int s; //静态局部变量
enum Loc { a=1024,b };
class Bar { //局部类
public:
Loc locv; //正确,允许使用局部类型
int barv;
void setBar(Loc l=a) { //正确,默认参数值为枚举类型的枚举器a
barv=v; //错误,v是函数fun的形参
barv=::v; //正确,使用全局v
barv=s; //正确,使用静态局部变量
locv=b; //正确,使用枚举器b
}
};
}
对象的生命期
按生命期的不同,对象可分为如下四种:
(1)局部对象
局部对象在运行函数时被创建,调用构造函数;当函数运行结束时被释放,调用析构函数
(2)静态局部对象
静态局部对象在程序执行函数第一次经过该对象的定义语句时被创建,调用构造函数。这种对象一旦被创建,在程序结束前都不会撤销。即使定义静态局部对象的函数结束时,静态局部对象也不会撤销。在该函数被多次调用的过程中,静态局部对象会持续存在并保持它的值
静态局部对象在程序运行结束时被释放,调用析构函数
(3)全局对象
全局对象在程序开始运行时,main运行前创建对象,并调用构造函数;在程序运行结束时被释放,调用析构函数
(4)自由存储对象
用new分配的自由存储对象在new运算时创建对象,并调用构造函数;在delete运算时被释放,调用析构函数。自由存储对象一经new运算创建,就会始终保持直到delete运算时,即使程序运行结束它也不会自动释放
#include<iostream>
using namespace std;
class A {
public:
int n;
A(int _n=10) : n(_n) { cout<<"A("<<n<<")构造\t"; }
~A() { cout<<"A("<<n<<"(析构\t"; }
};
class B {
public:
int m;
B(int _n=20) : m(_n),a(_n) { cout<<"B("<<m<<")构造"<<endl; }
~B() { cout<<"B("<<m<<")析构"<<endl; }
A a; //类类型对象
};
B *gp, gb(30); //全局对象
void fun4() {
static B b41(41); //静态局部对象
B b42(42); //局部对象
gp = new B(43); //自由存储对象
}
void fun5() {
static B b51(51); //静态局部对象
B b52(52); //局部对象
}
B fun6(B b61) { //形参对象
delete gp; //释放自由存储对象
return b61;
}
int main()
{
cout<<"--------main start--------"<<endl;
fun4();
cout<<"\n--------fun4 endl--------"<<endl;
fun5();
cout<<"\n--------fun5 endl--------"<<endl;
B b71(71),b72(72); //局部对象
b72 = fun6(b71); //函数返回临时对象
cout<<"\n--------main end--------"<<endl;
return 0;
}