C++ 类作用域
类作用域基础概念
在C++ 中,类定义了一个新的作用域。当我们在类中声明成员(变量或函数)时,这些成员就处于类的作用域内。类作用域决定了类成员的可见性和访问规则。例如,考虑以下简单的类定义:
class MyClass {
private:
int privateVariable;
public:
void setVariable(int value) {
privateVariable = value;
}
int getVariable() {
return privateVariable;
}
};
在上述代码中,privateVariable
是一个私有成员变量,它的作用域仅限于 MyClass
类内部。setVariable
和 getVariable
是公有成员函数,它们也在 MyClass
的作用域内。这些成员函数可以访问 privateVariable
,因为它们在同一个类作用域中。而在类外部,privateVariable
是不可直接访问的,只有通过 setVariable
和 getVariable
这样的公有接口才能间接访问。
访问类成员
1. 通过对象访问公有成员
当我们创建类的对象后,可以使用对象名和点运算符(.
)来访问类的公有成员。例如:
int main() {
MyClass obj;
obj.setVariable(10);
int value = obj.getVariable();
return 0;
}
在上述代码中,obj
是 MyClass
的对象,通过 obj.setVariable(10)
调用公有成员函数 setVariable
来设置 privateVariable
的值,然后通过 obj.getVariable()
获取其值。
2. 成员函数内部访问成员
成员函数可以直接访问类的其他成员,无需通过对象。例如,在 setVariable
函数中,直接对 privateVariable
进行赋值操作。这是因为成员函数默认处于类的作用域中,它对类的所有成员(包括私有成员)具有访问权限。
类作用域中的名字查找
当在类中查找一个名字时,C++ 遵循特定的规则。首先,编译器会在类的作用域中查找该名字。如果在类中没有找到,它会继续在外层作用域中查找(如果存在外层作用域)。例如:
int globalVariable = 100;
class NameLookupExample {
private:
int localVariable;
public:
void setLocalVariable(int value) {
localVariable = value;
}
int getLocalVariable() {
return localVariable;
}
int getCombinedValue() {
return localVariable + globalVariable;
}
};
在 getCombinedValue
函数中,localVariable
首先在类的作用域中被找到,而 globalVariable
在类作用域中未找到,于是编译器在外层全局作用域中找到了它。
嵌套类
1. 嵌套类定义
一个类可以在另一个类的内部定义,这种类称为嵌套类。嵌套类定义了一个新的作用域,该作用域嵌套在包含它的类的作用域内。例如:
class OuterClass {
private:
int outerVariable;
public:
class InnerClass {
private:
int innerVariable;
public:
InnerClass(int value) : innerVariable(value) {}
int getInnerVariable() {
return innerVariable;
}
};
OuterClass(int value) : outerVariable(value) {}
int getOuterVariable() {
return outerVariable;
}
};
在上述代码中,InnerClass
是 OuterClass
的嵌套类。InnerClass
的作用域嵌套在 OuterClass
的作用域内。
2. 嵌套类的访问规则
InnerClass
可以访问 OuterClass
的公有和保护成员,但不能直接访问私有成员,除非 InnerClass
被声明为 OuterClass
的友元类。而 OuterClass
对 InnerClass
的成员没有特殊的访问权限,它只能通过 InnerClass
的公有接口来访问 InnerClass
的成员。例如:
int main() {
OuterClass outer(20);
OuterClass::InnerClass inner(30);
int outerValue = outer.getOuterVariable();
int innerValue = inner.getInnerVariable();
return 0;
}
在上述代码中,通过 OuterClass::InnerClass
这种语法来声明 InnerClass
类型的对象。
作用域解析运算符(::
)与类
1. 访问类的静态成员
类的静态成员属于类本身,而不是类的对象。我们可以使用作用域解析运算符来访问类的静态成员,即使没有创建类的对象。例如:
class StaticExample {
private:
static int staticVariable;
public:
static void setStaticVariable(int value) {
staticVariable = value;
}
static int getStaticVariable() {
return staticVariable;
}
};
int StaticExample::staticVariable = 0;
int main() {
StaticExample::setStaticVariable(50);
int value = StaticExample::getStaticVariable();
return 0;
}
在上述代码中,StaticExample::staticVariable
用于在类外定义和初始化静态成员变量。StaticExample::setStaticVariable
和 StaticExample::getStaticVariable
用于访问静态成员函数,无需创建 StaticExample
的对象。
2. 定义类外的成员函数
当我们在类外定义成员函数时,需要使用作用域解析运算符来指定该函数属于哪个类。例如:
class OutOfClassDefinition {
private:
int memberVariable;
public:
void setMemberVariable(int value);
int getMemberVariable();
};
void OutOfClassDefinition::setMemberVariable(int value) {
memberVariable = value;
}
int OutOfClassDefinition::getMemberVariable() {
return memberVariable;
}
在上述代码中,OutOfClassDefinition::setMemberVariable
和 OutOfClassDefinition::getMemberVariable
使用作用域解析运算符来表明这些函数是 OutOfClassDefinition
类的成员函数。
类作用域与继承
1. 基类与派生类的作用域关系
当一个类从另一个类派生时,派生类继承了基类的成员(根据继承方式决定访问权限)。派生类的作用域嵌套在基类的作用域之上。例如:
class BaseClass {
protected:
int baseVariable;
public:
BaseClass(int value) : baseVariable(value) {}
int getBaseVariable() {
return baseVariable;
}
};
class DerivedClass : public BaseClass {
private:
int derivedVariable;
public:
DerivedClass(int baseValue, int derivedValue) : BaseClass(baseValue), derivedVariable(derivedValue) {}
int getDerivedVariable() {
return derivedVariable;
}
int getCombinedValue() {
return baseVariable + derivedVariable;
}
};
在上述代码中,DerivedClass
从 BaseClass
公有派生。DerivedClass
可以访问 BaseClass
的保护成员 baseVariable
,因为在公有继承下,保护成员在派生类中仍然是保护的。DerivedClass
的成员函数 getCombinedValue
可以直接访问 baseVariable
和 derivedVariable
,因为它们都在 DerivedClass
的作用域(或其基类作用域)内。
2. 隐藏基类成员
如果派生类中声明了一个与基类成员同名的成员,那么基类的该成员在派生类的作用域中被隐藏。例如:
class BaseHide {
public:
void print() {
std::cout << "Base class print" << std::endl;
}
};
class DerivedHide : public BaseHide {
public:
void print(int value) {
std::cout << "Derived class print with value: " << value << std::endl;
}
};
在上述代码中,DerivedHide
类中的 print(int value)
函数隐藏了 BaseHide
类中的 print()
函数。如果我们创建 DerivedHide
的对象并调用 print
,会调用 DerivedHide
中的版本。要调用基类的版本,可以使用作用域解析运算符:
int main() {
DerivedHide obj;
obj.print(10);
obj.BaseHide::print();
return 0;
}
在上述代码中,obj.print(10)
调用派生类的 print
函数,而 obj.BaseHide::print()
调用基类的 print
函数。
类作用域中的 this 指针
1. this 指针的概念
每个非静态成员函数都有一个隐含的指针参数 this
,它指向调用该函数的对象。通过 this
指针,成员函数可以访问对象的成员变量和调用其他成员函数。例如:
class ThisPointerExample {
private:
int data;
public:
ThisPointerExample(int value) : data(value) {}
void setData(int value) {
this->data = value;
}
int getData() {
return this->data;
}
};
在上述代码中,setData
和 getData
函数中使用 this
指针来明确访问对象的 data
成员变量。虽然在这种情况下,不使用 this
指针也可以访问 data
,但在某些情况下,this
指针是必要的。
2. 使用 this 指针返回对象自身
this
指针还可以用于返回对象自身,这在链式调用等场景中非常有用。例如:
class ChainCallExample {
private:
int number;
public:
ChainCallExample(int value) : number(value) {}
ChainCallExample& increment() {
number++;
return *this;
}
ChainCallExample& decrement() {
number--;
return *this;
}
int getNumber() {
return number;
}
};
在上述代码中,increment
和 decrement
函数返回 *this
,这使得可以进行链式调用:
int main() {
ChainCallExample obj(5);
obj.increment().decrement().increment();
int result = obj.getNumber();
return 0;
}
在上述代码中,obj.increment().decrement().increment()
进行了连续的操作,每个操作返回对象自身,以便继续调用下一个成员函数。
类作用域中的友元
1. 友元函数
一个类可以将其他函数声明为友元,这样这些友元函数就可以访问该类的私有和保护成员。例如:
class FriendFunctionExample {
private:
int privateData;
public:
FriendFunctionExample(int value) : privateData(value) {}
friend void printPrivateData(FriendFunctionExample obj);
};
void printPrivateData(FriendFunctionExample obj) {
std::cout << "Private data: " << obj.privateData << std::endl;
}
在上述代码中,printPrivateData
函数被声明为 FriendFunctionExample
类的友元,因此它可以访问 FriendFunctionExample
类的私有成员 privateData
。
2. 友元类
一个类也可以将另一个类声明为友元,此时友元类的所有成员函数都可以访问该类的私有和保护成员。例如:
class FriendClass {
private:
int privateValue;
public:
FriendClass(int value) : privateValue(value) {}
friend class AccessFriendClass;
};
class AccessFriendClass {
public:
void printFriendValue(FriendClass obj) {
std::cout << "Friend class private value: " << obj.privateValue << std::endl;
}
};
在上述代码中,AccessFriendClass
被声明为 FriendClass
的友元类,因此 AccessFriendClass
的成员函数 printFriendValue
可以访问 FriendClass
的私有成员 privateValue
。
类模板与作用域
1. 类模板的作用域特性
类模板定义了一种通用的类结构,它的作用域规则与普通类类似,但有一些特殊之处。例如,考虑一个简单的类模板:
template <typename T>
class Stack {
private:
T* data;
int top;
int capacity;
public:
Stack(int size) : capacity(size), top(-1) {
data = new T[capacity];
}
~Stack() {
delete[] data;
}
void push(T value) {
if (top < capacity - 1) {
data[++top] = value;
}
}
T pop() {
if (top >= 0) {
return data[top--];
}
return T();
}
};
在上述代码中,Stack
类模板的成员函数定义在模板类的作用域内。当我们实例化类模板时,例如 Stack<int> intStack(5);
,编译器会根据模板参数 int
生成一个具体的 Stack<int>
类,其中的成员函数作用域属于生成的具体类。
2. 在类模板外定义成员函数
当在类模板外定义成员函数时,需要使用模板参数列表和作用域解析运算符。例如:
template <typename T>
class Queue {
private:
T* data;
int front;
int rear;
int capacity;
public:
Queue(int size);
~Queue();
void enqueue(T value);
T dequeue();
};
template <typename T>
Queue<T>::Queue(int size) : capacity(size), front(0), rear(0) {
data = new T[capacity];
}
template <typename T>
Queue<T>::~Queue() {
delete[] data;
}
template <typename T>
void Queue<T>::enqueue(T value) {
if ((rear + 1) % capacity != front) {
data[rear] = value;
rear = (rear + 1) % capacity;
}
}
template <typename T>
T Queue<T>::dequeue() {
if (front != rear) {
T value = data[front];
front = (front + 1) % capacity;
return value;
}
return T();
}
在上述代码中,Queue
类模板的成员函数在类模板外定义。template <typename T>
声明模板参数列表,Queue<T>::
使用作用域解析运算符表明这些函数属于 Queue<T>
类模板。
总结类作用域相关要点
- 类定义了一个新的作用域,类成员在该作用域内具有特定的可见性和访问规则。
- 可以通过对象访问公有成员,成员函数可以直接访问类的其他成员。
- 名字查找首先在类作用域进行,然后在外层作用域进行。
- 嵌套类定义了嵌套的作用域,有其特定的访问规则。
- 作用域解析运算符用于访问静态成员和在类外定义成员函数。
- 在继承关系中,派生类作用域嵌套在基类作用域之上,可能会隐藏基类成员。
this
指针用于在成员函数中访问对象自身。- 友元函数和友元类可以访问类的私有和保护成员。
- 类模板的作用域规则与普通类类似,但在类模板外定义成员函数需要特殊的语法。
理解类作用域对于编写正确、高效的C++ 代码至关重要,它影响着代码的组织结构、数据封装和访问控制等多个方面。在实际编程中,我们需要根据具体的需求和设计原则,合理运用类作用域的各种特性。