MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C++ 类作用域

2024-03-071.1k 阅读

类作用域基础概念

在C++ 中,类定义了一个新的作用域。当我们在类中声明成员(变量或函数)时,这些成员就处于类的作用域内。类作用域决定了类成员的可见性和访问规则。例如,考虑以下简单的类定义:

class MyClass {
private:
    int privateVariable;
public:
    void setVariable(int value) {
        privateVariable = value;
    }
    int getVariable() {
        return privateVariable;
    }
};

在上述代码中,privateVariable 是一个私有成员变量,它的作用域仅限于 MyClass 类内部。setVariablegetVariable 是公有成员函数,它们也在 MyClass 的作用域内。这些成员函数可以访问 privateVariable,因为它们在同一个类作用域中。而在类外部,privateVariable 是不可直接访问的,只有通过 setVariablegetVariable 这样的公有接口才能间接访问。

访问类成员

1. 通过对象访问公有成员

当我们创建类的对象后,可以使用对象名和点运算符(.)来访问类的公有成员。例如:

int main() {
    MyClass obj;
    obj.setVariable(10);
    int value = obj.getVariable();
    return 0;
}

在上述代码中,objMyClass 的对象,通过 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;
    }
};

在上述代码中,InnerClassOuterClass 的嵌套类。InnerClass 的作用域嵌套在 OuterClass 的作用域内。

2. 嵌套类的访问规则

InnerClass 可以访问 OuterClass 的公有和保护成员,但不能直接访问私有成员,除非 InnerClass 被声明为 OuterClass 的友元类。而 OuterClassInnerClass 的成员没有特殊的访问权限,它只能通过 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::setStaticVariableStaticExample::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::setMemberVariableOutOfClassDefinition::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;
    }
};

在上述代码中,DerivedClassBaseClass 公有派生。DerivedClass 可以访问 BaseClass 的保护成员 baseVariable,因为在公有继承下,保护成员在派生类中仍然是保护的。DerivedClass 的成员函数 getCombinedValue 可以直接访问 baseVariablederivedVariable,因为它们都在 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;
    }
};

在上述代码中,setDatagetData 函数中使用 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;
    }
};

在上述代码中,incrementdecrement 函数返回 *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> 类模板。

总结类作用域相关要点

  1. 类定义了一个新的作用域,类成员在该作用域内具有特定的可见性和访问规则。
  2. 可以通过对象访问公有成员,成员函数可以直接访问类的其他成员。
  3. 名字查找首先在类作用域进行,然后在外层作用域进行。
  4. 嵌套类定义了嵌套的作用域,有其特定的访问规则。
  5. 作用域解析运算符用于访问静态成员和在类外定义成员函数。
  6. 在继承关系中,派生类作用域嵌套在基类作用域之上,可能会隐藏基类成员。
  7. this 指针用于在成员函数中访问对象自身。
  8. 友元函数和友元类可以访问类的私有和保护成员。
  9. 类模板的作用域规则与普通类类似,但在类模板外定义成员函数需要特殊的语法。

理解类作用域对于编写正确、高效的C++ 代码至关重要,它影响着代码的组织结构、数据封装和访问控制等多个方面。在实际编程中,我们需要根据具体的需求和设计原则,合理运用类作用域的各种特性。