avatar

目录
324 - Nested class

Cpp Nest

🧑‍💻 User

In this lecture, we’re going to learn about nested classes, and this is a feature we have in C plusplus to allow us to declare classes inside other classes.If you look here, we have a class called Outer and it is our outer class.But if you look inside the private section here, you see that we have another class declaration andthe class is called inner.So we can set up things like this if it makes sense for our application.And this is really useful if you want to limit how people can create inner objects and you want themto only be able to do that inside another object.So this is going to become useful.But I personally think this design is really weird and confusing.People are going to have a hard time to really put together the relationship between your classes.But please note that this is my own personal preference if it makes sense for the problem you are tryingto solve with C plus plus.Okay, so this is really all about nesting.But before we head over to Visual Studio code and play with us, let’s see some facts about this one.The inner class is private.It’s objects can’t be created from the outside, like in Main.If you do that, you’re going to get a compiler error because you are trying to access something privateto the object.So if you do this, you’re going to get a compiler error.The outer class doesn’t have access to private members of the inner class.So this is a setup we have and you’re going to have this If you set up inner classes inside outsideclasses, the inner class is going to have access to private sections of the outer class so you canaccess the private member variables and inner can directly access static members of outer, but it can’taccess member variables without going through an object name.And these are some limitations you need to be aware of.I realized some of these things might not make sense yet, so we’re going to head over to Visual Studiocode and play with this a little more.Okay.Here we are in our working folder.The current project is nested classes.We are going to bring in our template file.So let’s do that and we’re going to put them in place.And let’s open this by dragging and dropping on top of Visual Studio code here.This is going to open our project.We’re going to create two files for the class that we want to create.We’re going to create the outer dot h file and the outer dot CPP file to hold our outer class.So let’s do that.We’re going to say outer dot H, this is the header and we’re going to say outer dot CPP.This is going to be our implementation file.I am going to put in my header file information.And what we have is really simple.We have the outer class, we have a public section, we have a private section.Inside the private section, we have a few member variables.We also have a static member variables.And if you go down, you’re going to see that we have our inner class inside.And this is nothing complicated really.It is a simple class.After the declaration of the class, we have to put a semicolon.This is the syntax that Cplusplus expects.And inside our inner function we have to do something with outer function.And we’re going to learn about this little things in a minute when we have put in place the CPP file.Okay, so let’s do that.We’re going to go to the CPP file and put in the implementation for our class.It is nothing complicated really.We just have the implementations for our two constructors that we have in our outer class.We have an on prem constructor and we have a constructor that takes two parameters and we’re going touse these two parameter constructor to do the work for the No parameter constructor here we are goingto delegate.Other than this, this is really simple.We are also going to implement the constructor for inner and you see that we have to use the syntaxlike this to say that the inner constructor is living inside the outer class.So you have to do things like this.If you take out this scope resolution operator, you’re going to get a compiler error because the compileris going to look for this in the header and it’s not going to know where to look because it doesn’tknow where this inner constructor lives.The way to tell the compiler that is to go through the scope resolution operator like this.So this is the first thing you need to know.If we go in our header file, we’re going to see that inner has only one constructor that takes oneparameter and that’s what we are using here.We are using initializer lists to do this.Okay, so let’s look at this a little more.Okay.So now that we have this set up here, let’s try to do some things with our inner class.The first thing you might want to do is to create an object of this class.Let’s do that.We’re going to head over to Main.cpp and we’re going to include outer because that’s the header thatcontains.Our thanks.And we’re going to try and go inside and say inner, okay, inner one, let’s say that and we’re goingto pass a parameter maybe 5.6.I think that’s what this guy takes.Let’s check this.Let’s see what we take.Yes, we take a double parameter.And if we try to do this, this is not going to work because the compiler is going to look to the outsidein here and it’s not going to see any inner class.We don’t have any class called inner declared in the outside scope in the outer dot h file here.So it’s not going to find it and we’re going to get a compiler error.It’s going to say, I don’t know what inner is, let’s do that.We’re going to pass this through GCC.You’re going to see for yourself and it’s going to say inner was not declared in this scope.We don’t know what inner is.So you might think, huh, inner is declared inside another class.What if I use a scope resolution?Operator, let’s do that.We’re going to say outer and we’re going to put a scope resolution operator And the error is going tochange, but you’re going to see that it’s going to know what inner is.It’s going to give us a different compiler error.Let’s build and show you that.And it’s going to say outer inner is private within this context because inner lives in the privatesection of outer, you can see that here we can’t access it from the outside.And that includes trying to create objects from this because this is going to call the constructor ofinner, but the inner constructor is leaving and the private section of outer, so we can’t really accessit.And if we do this, we’re going to get this compiler error that we are seeing here.You might want to try and come here and make this little guy public.If you do this, this is going to work.So let’s do that.We’re going to build again and use the GCC to do that.Now, this is working because if you issue a call like this, it’s going to search for the constructorfor inner.It’s going to go in this header file, it’s going to search and it’s going to find, Aha, I have aconstructor here and I can call it from the main function because it is public and everything is goingto work.Let’s see if we have a member function on inner that we can call, let’s say get double and this isgoing to print the static int and we’re going to see that printed out on the console.So let’s do that.We’re going to say get double and we can say inner one, get double and we’re going to build and seeif we can print and see something on the console.Okay, the build is good.We can clear and run Rooster and if we do this, we have our variable, which is 45.If we go in outer, we’re going to see that this little guy is 45 and we are accessing that from ourmember function of the inner class.And this is really cool.Okay, so now you note that if you set up your inner class to be in a private section, you want tobe able to use that from the outside by creating objects like we do in here.So you should note that another thing I want to bring to your attention is that the outer class heredoesn’t have access to private members of the inner class.Let’s go and see what we can have here.We have Innovare and inside the do something method of outer Here we have an inner object.We can do that because this is a member function of outer, so it can use the private members of inner.This is not going to give us a problem.But if we try to access a private member from the outer class, we’re going to get a compiler errorand outer doesn’t have access to private parts of the inner.We have to go through setters and getters to do that if you want to access these guys.So this is the message here.Let’s try and build and we’re going to see a compiler error.So we’re going to see inner Var is private within this context.We can’t really access that here.If you do that, you’re going to get a compiler error, but you can go through a getter function tohave access to whatever you want.So for example, if we go in inner and set up a member function, we can return the member variablefrom that member function and have access to that inside our dosomething function here.So for example, we can go down here and say get double and try to print that out.Why not?And we can see the member function called here on top get double.This is how you can use this.So if we call, do something on our outer object that we create, we’re going to see this message here.So let’s try this out.Let’s build and show you that this is working first and we have a problem.Class, outer inner is private in this context.We can’t create objects of outer.So this is the problem we have in main.We can take this out and build again.And before we build, we’re going to create an object of outer.So we’re going to say outer one and we’re going to give it whatever it wants.Let’s see what the constructor of outer is taking.We’re going to pass in an INT and the double.Let’s do that.We’re.Going to pass in a ten and a 20.1.Why not?And we can do something with our we can say outer one, do something.And if we build this should world just fine, we’re going to use GCC for that.You’re going to see that the world is good.We can clear and run rooster and created an inner object with a value of ten.So let’s look at this.We’re going to see what we have in the constructor.We do something.We create an inner object, we put in a value, and then we’re going to print the member variable througha getter that we have here.This is the setup you can use to have access to member variables of your private nested class if youhappen to need that in your application.But instead, class is going to have access to private members of the global class and this is goingto work if you go through the object names.So let’s go down here and really show you that we have a function called Do something with Outer andwe pass a pointer to outer and then we’re going to go through this pointer to access the member variablesof outer.And we are doing this from the inside of the inner object.And this is going to work.So to be able to try this out, we’re going to go in a method that we have in outer which is going tocreate our object and we’re going to call the do something with outer method and we’re going to passthe this object because that’s going to be the outer object we will be working on.So we’re going to do that.And let’s comment out all this we had on top here because it’s going to be confusing if we have thisand we’re going to see what we see in this call here.So let’s build and see that this world’s the world is good.And if we run this, let’s do that.We’re going to run this.And you see, this is inner access.Private parts of outer MVR is one.And we’re going to see that printed out here.I realize this is really convoluted, but you need to know this.If you’re going to use nested classes in your design.So we are really accessing a private member of the outer class, which is mvar here inside an innerclass object.And this is going to work.Another thing you should know is that from your nested class you have access to static members of theenclosing class without going through the object name.And we are accessing this static member function here without really going through an object name.And you’re going to see that this is going to work.If we’re trying to compile this, this is going to work just fine.Let’s use GCC and show you that and you see that the world is good.So this is really all I have to share in terms of nesting the classes inside other classes.Again, these are a few things you need to remember.When inner is private, you can’t create objects of inner from the outside.If you do that, you’re going to get a compiler error.Outer doesn’t have access to private sections of nested classes.If you’re trying to access that, you’re going to get a compiler error.But inner has access to the private section of the enclosing class and the inner can also directly accessstatic members of the outer class, but it can’t access regular member variables which are not staticwithout going through object names.And this is really all you need to know.If you keep these things in your mind, you’re going to be able to use nested classes fairly easilyin your career as a C plus plus developer.We are going to stop here in this lecture and the next one we’re going to try and zoom in on in classmember initialization.Go ahead and finish up here and meet me there.


🤖 Assistant

你好!很高兴能帮助你学习 C++ 中一个非常有趣的特性——嵌套类。作为你的C++专业开发导师和全球最好的老师,我将用最清晰、最易懂的方式,带你一步步掌握这个知识点。


C++ 嵌套类 [Nested classes] 深度解析与实践

内容概要:
本课程将详细讲解 C++ 中的嵌套类 [Nested classes]:即在一个类 [Class] 的内部定义另一个类。我们将探讨它的用途、访问规则、如何定义其成员,并通过详细的代码示例、问答卡片和常见误区分析,确保你彻底理解并能熟练运用这一概念。


1. 什么是嵌套类 [Nested classes]?

想象一下,你有一个大盒子 (外部类 [Outer class]),然后在这个大盒子里面,你又放了一个小盒子 (内部类 [Inner class])。这就是 C++ 嵌套类的本质:一个类 [Class] 在另一个类的内部被声明。

定义:
一个嵌套类 [Nested classes] 是指在另一个类(我们称之为外部类 [Outer class])的声明范围内定义的类。它通常被声明在外部类的 public(公有 [Public])、protectedprivate(私有 [Private])部分。

为何使用嵌套类?
虽然在某些场景下,嵌套类的设计可能看起来有点“怪异”或复杂,但它有其独特的应用场景:

  • 逻辑组织与封装: 当一个类的存在与另一个类紧密相关,且逻辑上是后者的一个组成部分时,将其声明为嵌套类可以更好地体现这种从属关系,并增强封装性。例如,一个 LinkedList 类内部可能会有一个 Node(节点)类,Node 只有在 LinkedList 内部才有意义。
  • 限制对象创建: 这是嵌套类的一个重要用途。你可以将内部类 [Inner class] 声明为私有 [Private],从而限制只有外部类 [Outer class] 才能创建它的对象,外部代码无法直接访问和创建内部类的实例。这为程序设计提供了一种强大的控制机制。

设计考量:
课程中提到,这种设计可能“奇怪且令人困惑”,这确实是许多开发者的个人看法。它的主要缺点可能是增加了代码的复杂度和可读性,尤其是在层级较深或滥用时。因此,在使用嵌套类时,务必权衡其带来的组织优势和可能引入的复杂性,确保它能真正解决你的问题,而不是为了用而用。

2. 嵌套类的关键特性与访问规则

理解嵌套类最重要的部分是它与外部类之间的访问权限。这有点像“家庭关系”:

2.1 内部类 [Inner class] 的私有 [Private] 性

  • 核心规则: 如果内部类 [Inner class] 被声明在外部类 [Outer class] 的私有 [Private] 部分,那么在外部类的外部(比如 main 函数中),你将无法直接创建该内部类 [Inner class] 的对象。尝试这样做会导致编译错误 [Compiler error]。

  • 示例代码(尝试创建私有内部类对象 -> 编译错误):
    假设 Outer::InnerOuter 类的 private: 部分声明。

    Outer.h 文件:

    cpp
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    #ifndef OUTER_H
    #define OUTER_H

    class Outer {
    public:
    Outer(int val1, double val2);
    void doSomething(); // 外部类的一个方法

    private:
    int m_var1;
    double m_var2;
    static int s_outer_static_var; // 外部类的静态成员变量 [Static member variables]

    // 内部类 [Inner class] 声明在 private 部分
    class Inner {
    public:
    Inner(double val);
    void getDouble();
    void doSomethingWithOuter(Outer* outer_obj); // 访问外部类的方法
    void accessOuterStatic(); // 访问外部类的静态成员
    private:
    double m_inner_val;
    // int m_inner_private_var; // 内部类的私有成员,用于后续示例
    };

    Inner m_inner_obj; // 外部类内部可以创建内部类对象

    };

    #endif // OUTER_H

    Outer.cpp 文件:

    cpp
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    #include "Outer.h"
    #include <iostream>

    // 初始化外部类的静态成员变量 [Static member variables]
    int Outer::s_outer_static_var = 100;

    // 外部类构造函数 [Constructor]
    Outer::Outer(int val1, double val2) : m_var1(val1), m_var2(val2), m_inner_obj(val2) {
    std::cout << "Outer对象已创建,m_var1: " << m_var1 << ", m_var2: " << m_var2 << std::endl;
    }

    // 内部类构造函数 [Constructor] 的定义需要使用作用域解析运算符 [Scope Resolution Operator]
    // 语法: 外部类名::内部类名::成员名
    Outer::Inner::Inner(double val) : m_inner_val(val) {
    std::cout << "Inner对象已创建,值为: " << m_inner_val << std::endl;
    }

    void Outer::Inner::getDouble() {
    std::cout << "Inner的成员变量 m_inner_val: " << m_inner_val << std::endl;
    }

    void Outer::doSomething() {
    std::cout << "Outer::doSomething() 被调用." << std::endl;
    // 外部类可以访问内部类的公有方法
    m_inner_obj.getDouble();
    // 尝试访问内部类的私有成员 (假设 Inner 有一个 private int m_inner_private_var;)
    // m_inner_obj.m_inner_private_var = 10; // 错误:外部类不能直接访问内部类的私有成员
    }

    // 内部类访问外部类私有成员的示例
    void Outer::Inner::doSomethingWithOuter(Outer* outer_obj) {
    if (outer_obj) {
    // 内部类可以访问外部类对象的私有成员变量 [Member variables] (通过外部类对象指针/引用)
    std::cout << "Inner访问Outer的m_var1: " << outer_obj->m_var1 << std::endl;
    std::cout << "Inner访问Outer的m_var2: " << outer_obj->m_var2 << std::endl;
    }
    }

    // 内部类访问外部类静态成员的示例
    void Outer::Inner::accessOuterStatic() {
    // 内部类可以直接访问外部类的静态 [Static] 成员,无需通过对象实例
    std::cout << "Inner访问Outer的静态变量 s_outer_static_var: " << s_outer_static_var << std::endl;
    }

    main.cpp 文件(导致编译错误的部分):

    cpp
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #include "Outer.h"
    #include <iostream>

    int main() {
    // 尝试直接创建私有内部类 [Inner class] 的对象
    // Outer::Inner inner_obj_main(99.9); // 编译错误!因为 Outer::Inner 是私有的

    std::cout << "程序继续执行..." << std::endl;

    return 0;
    }

    编译输出(类似):
    error: ‘class Outer::Inner’ is private within this context

  • 如何变为可访问(改为公有 [Public]):
    如果你希望在外部能够创建内部类 [Inner class] 的对象,你需要将它声明在外部类 [Outer class] 的 public:(公有 [Public])或 protected: 部分。

  • 示例代码(改为公有后可创建对象):
    修改 Outer.h,将 class Inner 移动到 public: 部分。

    cpp
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    // Outer.h (部分修改)
    #ifndef OUTER_H
    #define OUTER_H

    class Outer {
    public:
    Outer(int val1, double val2);
    void doSomething();

    // 内部类 [Inner class] 声明在 public 部分
    class Inner { // 现在是公有 [Public] 的了
    public:
    Inner(double val);
    void getDouble();
    void doSomethingWithOuter(Outer* outer_obj);
    void accessOuterStatic();
    private:
    double m_inner_val;
    };

    private:
    int m_var1;
    double m_var2;
    static int s_outer_static_var;

    // 外部类内部依然可以创建内部类对象
    Inner m_inner_obj;
    };

    #endif // OUTER_H

    main.cpp 文件(现在可以成功编译和运行):

    cpp
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    #include "Outer.h"
    #include <iostream>

    int main() {
    // 现在可以成功创建公有 [Public] 内部类 [Inner class] 的对象
    // 注意:即使 Inner 是公有的,你仍然需要使用 外部类名::内部类名 的形式来引用它。
    Outer::Inner inner_obj_main(5.6);
    inner_obj_main.getDouble(); // 调用其公有方法

    Outer outer_instance(10, 20.1);
    outer_instance.doSomething();

    // 通过外部类对象,调用内部类方法访问外部类成员
    // 注意:这里 inner_obj_main 并没有关联到 outer_instance,
    // 所以我们不能用 inner_obj_main.doSomethingWithOuter(&outer_instance);
    // 因为 main 函数中创建的 inner_obj_main 是独立的。
    // 如果想演示,可以通过 outer_instance.m_inner_obj.doSomethingWithOuter(&outer_instance);
    // 但 m_inner_obj 是私有的,所以我们只能通过 Outer 的公有方法来调用。
    // 例如,在 Outer::doSomething() 中调用 m_inner_obj.doSomethingWithOuter(this);

    std::cout << "--- 演示内部类访问外部类 ---" << std::endl;
    outer_instance.doSomething(); // doSomething 内部会创建并操作 inner 对象

    return 0;
    }

    Outer.cppOuter::doSomething 的修改以展示内部类访问外部类:

    cpp
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // ... (Outer.cpp 部分)
    void Outer::doSomething() {
    std::cout << "Outer::doSomething() 被调用." << std::endl;
    m_inner_obj.getDouble();
    // 在这里调用内部类的方法,并传入当前外部类对象(this指针)
    m_inner_obj.doSomethingWithOuter(this); // 内部类访问外部类的私有非静态成员
    m_inner_obj.accessOuterStatic(); // 内部类访问外部类的静态成员
    }
    // ...

    运行输出(类似):

    Code
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    Inner对象已创建,值为: 5.6
    Inner的成员变量 m_inner_val: 5.6
    Outer对象已创建,m_var1: 10, m_var2: 20.1
    Outer::doSomething() 被调用.
    Inner的成员变量 m_inner_val: 20.1
    Inner访问Outer的m_var1: 10
    Inner访问Outer的m_var2: 20.1
    Inner访问Outer的静态变量 s_outer_static_var: 100
    --- 演示内部类访问外部类 ---
    Outer::doSomething() 被调用.
    Inner的成员变量 m_inner_val: 20.1
    Inner访问Outer的m_var1: 10
    Inner访问Outer的m_var2: 20.1
    Inner访问Outer的静态变量 s_outer_static_var: 100

2.2 外部类 [Outer class] 对内部类 [Inner class] 的访问

  • 核心规则: 外部类能直接访问内部类 [Inner class] 的私有 [Private] 成员变量 [Member variables] 或成员函数 [Member functions]。这遵循了类封装的基本原则。如果需要访问,必须通过内部类 [Inner class] 提供的公有 [Public] 接口(如 getter/setter 方法)。

  • 示例代码(外部类尝试访问内部类私有成员 -> 编译错误):
    Outer.hclass Inner 中,假设我们有一个 private double m_inner_val;
    如果我们在 Outer::doSomething() 中尝试 std::cout << m_inner_obj.m_inner_val; 就会导致编译错误。

    cpp
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // Outer.h (假设 Inner 类的 private 部分有 m_inner_val)
    // ...
    class Outer {
    // ...
    class Inner {
    public:
    // ...
    private:
    double m_inner_val; // 这是私有的
    };
    // ...
    };
    cpp
    1
    2
    3
    4
    5
    6
    7
    // Outer.cpp (在 Outer::doSomething() 中尝试非法访问)
    void Outer::doSomething() {
    std::cout << "Outer::doSomething() 被调用." << std::endl;
    // m_inner_obj.m_inner_val = 100.0; // 编译错误!'m_inner_val' is private within this context
    // std::cout << "尝试访问内部类私有变量: " << m_inner_obj.m_inner_val << std::endl; // 编译错误
    m_inner_obj.getDouble(); // 正确:通过公有 [Public] 方法访问
    }
  • 通过 getter 访问:
    正确的方式是内部类 [Inner class] 提供一个公有 [Public] 的 getter 方法来获取其私有 [Private] 成员的值。

    cpp
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // Outer.h (在 Inner 类中添加公有 [Public] getter)
    // ...
    class Outer {
    public:
    // ...
    class Inner {
    public:
    Inner(double val);
    void getDouble();
    double getInnerValue() const { return m_inner_val; } // 添加 getter 方法
    // ...
    private:
    double m_inner_val;
    };
    // ...
    };
    cpp
    1
    2
    3
    4
    5
    6
    7
    8
    // Outer.cpp (在 Outer::doSomething() 中通过 getter 访问)
    void Outer::doSomething() {
    std::cout << "Outer::doSomething() 被调用." << std::endl;
    std::cout << "通过getter访问Inner的m_inner_val: " << m_inner_obj.getInnerValue() << std::endl; // OK
    m_inner_obj.getDouble();
    m_inner_obj.doSomethingWithOuter(this);
    m_inner_obj.accessOuterStatic();
    }

2.3 内部类 [Inner class] 对外部类 [Outer class] 的访问

  • 核心规则: 这是嵌套类中一个非常重要的特性。内部类 [Inner class] 可以访问外部类 [Outer class] 的所有成员(包括私有 [Private] 和保护 protected 成员),无论外部类成员的访问权限如何。

    • 访问非静态 [Static] 成员变量 [Member variables]: 必须通过外部类 [Outer class] 的对象实例(指针或引用)来访问。这是因为非静态成员属于特定的对象实例,内部类对象本身没有这个实例。
    • 访问静态 [Static] 成员变量 [Member variables]: 可以直接访问,无需通过外部类对象实例。因为静态 [Static] 成员属于类本身,而非特定对象。
  • 示例代码(内部类访问外部类私有非静态成员 -> 需要外部类对象):
    Outer::Inner::doSomethingWithOuter(Outer* outer_obj) 方法中演示。

    cpp
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // Outer.cpp (部分)
    // 内部类访问外部类私有成员的示例
    void Outer::Inner::doSomethingWithOuter(Outer* outer_obj) {
    if (outer_obj) {
    // 内部类可以访问外部类对象的私有成员变量 [Member variables] (通过外部类对象指针/引用)
    std::cout << "Inner访问Outer的m_var1: " << outer_obj->m_var1 << std::endl; // m_var1 是 Outer 的私有成员
    std::cout << "Inner访问Outer的m_var2: " << outer_obj->m_var2 << std::endl; // m_var2 是 Outer 的私有成员
    }
    }

    可以看到,m_var1m_var2 尽管是 Outer私有 [Private] 成员,但 Inner 类的方法可以通过 outer_obj 指针轻松访问它们。

  • 示例代码(内部类访问外部类静态成员 -> 直接访问):
    Outer::Inner::accessOuterStatic() 方法中演示。

    cpp
    1
    2
    3
    4
    5
    6
    7
    // Outer.cpp (部分)
    // 内部类访问外部类静态成员的示例
    void Outer::Inner::accessOuterStatic() {
    // 内部类可以直接访问外部类 [Outer class] 的静态 [Static] 成员,无需通过对象实例
    std::cout << "Inner访问Outer的静态变量 s_outer_static_var: " << s_outer_static_var << std::endl;
    // s_outer_static_var 是 Outer 的静态私有成员,但 Inner 可以直接访问
    }

    这里 s_outer_static_varOuter静态私有 [Static private] 成员,Inner 类可以直接使用其名称访问,不需要 Outer::s_outer_static_var 这样的前缀,也不需要 outer_obj->s_outer_static_var

2.4 定义内部类 [Inner class] 成员函数 [Member functions]

  • 核心规则: 当你在外部类 [Outer class] 定义之外(通常在 .cpp 实现文件)定义内部类 [Inner class] 的成员函数 [Member functions] 时,你需要使用作用域解析运算符 [Scope Resolution Operator] :: 来明确指定其所属的类层级。
    语法格式为:外部类名::内部类名::成员函数名

  • 示例代码(定义内部类构造函数 [Constructor]):

    cpp
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // Outer.cpp (部分)
    // 内部类构造函数 [Constructor] 的定义需要使用作用域解析运算符 [Scope Resolution Operator]
    // 语法: 外部类名::内部类名::成员名
    Outer::Inner::Inner(double val) : m_inner_val(val) {
    std::cout << "Inner对象已创建,值为: " << m_inner_val << std::endl;
    }

    void Outer::Inner::getDouble() {
    std::cout << "Inner的成员变量 m_inner_val: " << m_inner_val << std::endl;
    }
    // 其他 Inner 成员函数也以类似方式定义

    如果你尝试省略 Outer::,编译器将无法找到 Inner 类,从而导致编译错误 [Compiler error]。

3. 代码示例

上面所有的讲解都包含了详细的代码示例,你可以将它们组合起来,形成一个完整的项目。

文件结构:

  • Outer.h:包含 Outer 类及其嵌套 Inner 类的声明。
  • Outer.cpp:包含 OuterInner 类成员函数的实现。
  • main.cpp:包含 main 函数,用于测试。

完整代码已在 2.1-2.3 节中提供。

4. 问答卡片 [QA Flash Cards]

以下是一些关键知识点的问答卡片,帮助你巩固记忆:

Q1: 什么情况下,你无法在 main 函数中直接创建一个内部类 [Inner class] 的对象?
A1:内部类 [Inner class] 被声明在外部类 [Outer class] 的 private私有 [Private])部分时。

Q2: 外部类 [Outer class] 可以直接访问内部类 [Inner class] 的私有 [Private] 成员变量 [Member variables] 吗?
A2: 不可以。外部类必须通过内部类提供的公有 [Public] 接口(如 getter/setter 方法)来访问其私有成员。

Q3: 内部类 [Inner class] 可以访问外部类 [Outer class] 的私有 [Private] 成员变量 [Member variables] 吗?如果可以,需要什么条件?
A3: 可以内部类 [Inner class] 可以访问外部类 [Outer class] 的所有成员(包括私有 [Private])。但访问非静态 [Static] 成员时,需要通过外部类 [Outer class] 的对象实例(指针或引用)。

Q4: 内部类 [Inner class] 如何访问外部类 [Outer class] 的静态 [Static] 成员变量 [Member variables]?
A4: 内部类 [Inner class] 可以直接访问外部类的静态 [Static] 成员,无需通过外部类对象实例。

Q5: 在 .cpp 文件中定义内部类 [Inner class] 的构造函数 [Constructor] 时,需要注意什么语法?
A5: 需要使用作用域解析运算符 [Scope Resolution Operator] ::,语法为 外部类名::内部类名::构造函数名。例如 Outer::Inner::Inner(double val)

5. 常见误解与错误 [Common Misunderstanding or Mistakes]

  • 误解1: 只要内部类是公有的,就可以直接 Inner obj;
    • 纠正: 即使内部类 [Inner class] 是公有 [Public] 的,你仍然需要使用外部类 [Outer class] 的作用域 [Scope] 来引用它,即 Outer::Inner obj;。编译器需要知道 Inner 是哪个 Inner
  • 误解2: 外部类和内部类之间共享所有成员,可以互相直接访问私有成员。
    • 纠正: 这是一个单向的关系。内部类 [Inner class] 可以访问外部类 [Outer class] 的私有成员,但外部类 [Outer class] 不能直接访问内部类 [Inner class] 的私有成员。
  • 误解3: 内部类可以随意访问外部类的非静态成员,就像它自己的成员一样。
    • 纠正: 访问外部类 [Outer class] 的非静态 [Static] 成员时,内部类 [Inner class] 必须通过外部类 [Outer class] 的对象实例来访问(例如通过传入 this 指针)。只有静态 [Static] 成员可以直接访问。这是因为非静态成员是与特定对象绑定的。
  • 误解4: 忘了在 .cpp 文件中为内部类 [Inner class] 的成员函数 [Member functions] 添加外部类作用域 [Scope]。
    • 纠正: 定义内部类 [Inner class] 成员函数时,务必使用 Outer::Inner:: 这样的前缀,否则会导致编译错误 [Compiler error],因为编译器找不到对应的类。

6. 编程练习 [Coding Exercise]

好的,现在轮到你来动手了!请完成下面的代码,实现一个简单的外部类 [Outer class] 和内部类 [Inner class],并遵循以下要求:

  1. 创建一个外部类 Car
  2. Car 类的 private私有 [Private])部分,声明一个内部类 Engine
  3. Car 类应该有一个私有 [Private] 成员变量 [Member variables] m_brand (字符串类型,表示品牌)。
  4. Engine 类应该有一个私有 [Private] 成员变量 [Member variables] m_horsepower (整型,表示马力)。
  5. Engine 类应该有一个公有 [Public] 构造函数 [Constructor],接收一个 horsepower 参数。
  6. Engine 类应该有一个公有 [Public] 方法 start(),在这个方法中:
    • 打印 Enginem_horsepower
    • 尝试访问其所属 Car 对象的 m_brand,并打印出来(你需要传入 Car 对象指针才能做到这一点)。
  7. Car 类应该有一个公有 [Public] 构造函数 [Constructor],接收 brandhorsepower 参数,并在其中创建 Engine 类的对象。
  8. Car 类应该有一个公有 [Public] 方法 drive(),在这个方法中,调用其内部 Engine 对象的 start() 方法,并传入 Car 对象的 this 指针。

请在下面的代码块中填写缺失的部分,使得代码能够成功编译并运行,输出预期的结果。

Car.h

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#ifndef CAR_H
#define CAR_H

#include <string> // 用于字符串类型

class Car {
public:
// TODO 1: 声明Car类的公有 [Public] 构造函数 [Constructor],接收品牌和马力参数
Car(const std::string& brand, int horsepower);

// TODO 2: 声明Car类的公有 [Public] 方法 drive()
void drive();

private:
std::string m_brand; // Car的私有成员变量 [Member variables]
// TODO 3: 在private部分声明内部类 [Inner class] Engine
class Engine {
public:
// TODO 4: 声明Engine类的公有 [Public] 构造函数 [Constructor],接收马力参数
Engine(int horsepower);

// TODO 5: 声明Engine类的公有 [Public] 方法 start(),接收一个Car类的指针
void start(Car* car_obj);

private:
int m_horsepower; // Engine的私有成员变量 [Member variables]
};

Engine m_engine; // Car内部的Engine对象
};

#endif // CAR_H

Car.cpp

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include "Car.h"
#include <iostream>

// TODO 6: 定义Car类的构造函数 [Constructor]
Car::Car(const std::string& brand, int horsepower)
// 初始化列表:初始化m_brand,并在构造函数中创建Engine对象
: m_brand(brand), m_engine(horsepower) { // 注意这里m_engine的初始化
std::cout << "Car对象 '" << m_brand << "' (马力: " << horsepower << ") 已创建." << std::endl;
}

// TODO 7: 定义Car类的drive()方法
void Car::drive() {
std::cout << "Car正在启动..." << std::endl;
// 调用内部Engine对象的start()方法,并传入当前的Car对象指针
m_engine.start(this);
}

// TODO 8: 定义Engine类的构造函数 [Constructor] (注意作用域解析运算符 [Scope Resolution Operator])
Car::Engine::Engine(int horsepower) : m_horsepower(horsepower) {
std::cout << "Engine对象 (马力: " << m_horsepower << ") 已创建." << std::endl;
}

// TODO 9: 定义Engine类的start()方法 (注意作用域解析运算符 [Scope Resolution Operator])
void Car::Engine::start(Car* car_obj) {
std::cout << "引擎启动!马力: " << m_horsepower << std::endl;
if (car_obj) {
// 内部类 [Inner class] 访问外部类 [Outer class] 的私有成员变量 [Member variables]
std::cout << "这是 " << car_obj->m_brand << " 的引擎." << std::endl; // 访问Car的m_brand
}
}

main.cpp

cpp
1
2
3
4
5
6
7
8
9
10
11
12
#include "Car.h"
#include <iostream>

int main() {
// TODO 10: 在main函数中创建Car对象并调用drive()方法
Car myCar("Tesla", 800);
myCar.drive();

std::cout << "\n程序结束." << std::endl;

return 0;
}

请填写上述代码中的 TODO 部分。当你完成并提供解决方案后,我会在下一轮回复中为你评分,给出正确答案和详细解释,并提供进一步的学习建议!


🧑‍💻 User

// 外部类构造函数 [Constructor]
Outer::Outer(int val1, double val2) : m_var1(val1), m_var2(val2), m_inner_obj(val2) {
std::cout << “Outer对象已创建,m_var1: “ << m_var1 << “, m_var2: “ << m_var2 << std::endl;
}
这里为啥m_inner_obj(val2) 直接可以work?而不是m_inner_obj(Inner(val2))


🤖 Assistant

这是一个非常好的问题!它涉及到 C++ 中类成员初始化列表([Member Initializer List])的关键语法和对象构造的原理。

让我们来详细解释一下:

当你在 Outer 类的构造函数中使用 m_inner_obj(val2) 时,你正在使用 C++ 的成员初始化列表 [Member Initializer List] 来初始化 m_inner_obj 这个成员变量。

  1. m_inner_obj(val2) 的含义 (直接初始化 [Direct Initialization])
    在成员初始化列表中,使用 成员变量名(参数) 这种语法,表示直接调用该成员变量所属类的构造函数 [Constructor] 来初始化它。

    • 在这里,m_inner_objOuter 类的一个成员,它的类型是 Inner
    • val2Outer 构造函数接收的一个 double 类型参数。
    • 所以,m_inner_obj(val2) 告诉编译器:请使用 val2 作为参数,来调用 m_inner_obj(即 Inner 类型对象)的构造函数。
    • 由于 Inner 类有一个构造函数 Inner(double val),它正好接收一个 double 类型的参数,所以 m_inner_obj(val2) 能够直接匹配并调用 Inner::Inner(double) 这个构造函数,从而正确地构造 m_inner_obj

    这种方式被称为直接初始化 [Direct Initialization],它在 m_inner_obj 被创建时就地构造,是最高效推荐的成员初始化方式。

  2. 为什么 m_inner_obj(Inner(val2)) 也可以(但通常不推荐)?

    • Inner(val2):这部分代码会首先创建一个临时Inner 对象。这个临时对象会调用 Inner::Inner(double) 构造函数,并用 val2 进行初始化。
    • m_inner_obj(...):然后,m_inner_obj 会尝试通过拷贝构造 [Copy Construction] 或移动构造 [Move Construction] 的方式,用这个临时对象来初始化自己。这需要 Inner 类有可用的拷贝构造函数(Inner(const Inner&))或移动构造函数(Inner(Inner&&))。
    • 在现代 C++ (C++11 及更高版本) 中,编译器通常会执行一种叫做拷贝消除 [Copy Elision] (或返回值优化 RVO) 的优化,这意味着它可能会避免实际的拷贝或移动操作,直接在 m_inner_obj 的内存位置构造对象,使其行为类似于 m_inner_obj(val2)

    但是,尽管它可能工作,这种写法通常不被推荐,原因如下:

    • 意图不明确: m_inner_obj(val2) 更清晰地表达了“使用 val2 参数直接构造 m_inner_obj”的意图。而 m_inner_obj(Inner(val2)) 看起来像是先创建一个临时对象,再进行拷贝/移动。
    • 潜在效率问题: 尽管有拷贝消除 [Copy Elision] 优化,但并不能保证所有编译器在所有情况下都能执行这种优化。如果没有优化,它会引入额外的临时对象创建和拷贝/移动的开销。
    • 要求额外构造函数: 如果 Inner 没有定义拷贝或移动构造函数(或它们被明确删除),那么 m_inner_obj(Inner(val2)) 就会导致编译错误。而 m_inner_obj(val2) 只需要 Inner 有一个接受 double 的构造函数即可。

总结:

m_inner_obj(val2) 直接调用 Inner 类的构造函数来初始化 m_inner_obj,这是 C++ 中初始化类类型成员的标准、高效且推荐的方式。

m_inner_obj(Inner(val2)) 涉及到创建临时对象和随后的拷贝/移动构造,尽管编译器可能进行优化,但语义上不如直接初始化清晰和高效。

所以在你的例子中,m_inner_obj(val2) 可以直接工作,因为 Inner 类有 Inner(double) 这样的构造函数来处理 val2 这个 double 类型的值。


评论