当前位置:   article > 正文

【C++笔记总结】面向对象编程——封装 |C++_在头文件中将目标类的定义放在私有部分

在头文件中将目标类的定义放在私有部分


前言

此文开启一个系列,旨在秋招前将所有C++笔记进行汇总总结。
参考答案:chatgpt

一、类的封装

1.1、公有,私有,保护

类中的数据称之为数据成员,类中的函数称之为成员函数
在C++中,类的成员变量和成员函数可以被访问控制符所限制,这些访问控制符分别是公有(public)、保护(protected)和私有(private)。举例1.1如下:

#include <iostream>

class MyClass {
public:
    void setData(int shuru) {
        privateData = shuru; // 类内使用私有成员:将参数赋值给私有成员
        protectedData = shuru * 2; // 类内使用保护成员:将参数的两倍赋值给保护成员
        std::cout << "setData: privateData = " << privateData << ", protectedData = " << protectedData << std::endl;
        // 在类的成员函数中访问保护成员和私有成员
        privateFunc();
        protectedFunc();
    }
    int getData() {
        return privateData; // 类内使用私有成员::直接使用私有成员的名称
    }

protected:
    int protectedData; // 保护成员
    void protectedFunc() {
        std::cout << "This is a protected function." << std::endl;
    } // 保护成员函数

private:
    int privateData; // 私有成员
    void privateFunc() {
        std::cout << "This is a private function." << std::endl;
    } // 私有成员函数
};

int main() {
    MyClass obj;//对象
    obj.setData(1234);//公有方法,可以在类外访问
    std::cout << "Data: " << obj.getData() << std::endl; 公有方法,可以在类外访问

    // obj.privateData = 456; // 私有成员不能在类的外部访问
    // obj.privateFunc(); // 私有函数不能在类的外部访问
    // obj.protectedData = 789; // 不能在类的外部访问保护成员
    // obj.protectedFunc(); // 保护函数不能在类的外部访问

    return 0;
}
  • 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

1)公有成员函数可以在类的外部访问,相当于是类的外部接口。;私有成员函数和保护成员函数不能在类的外部访问。
2)私有成员和保护成员的区别在于,私有成员只能在当前类的成员函数中访问,保护成员可以在当前类的成员函数和派生类的成员函数中访问。
3)公有成员函数可以访问类的所有成员,私有成员函数和保护成员函数也可以访问类的所有成员。

1.2、类的定义和类的实现相分离

在大型的C++工程文件里,类的定义和实现经常这样分开:(下面分成了三个文件)
MyClass.h:

MyClass.h:
#ifndef MYCLASS_H
#define MYCLASS_H

class MyClass {
public:
    void setData(int shuru);
    int getData();

protected:
    int protectedData;
    void protectedFunc();

private:
    int privateData;
    void privateFunc();
};

#endif

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

如果你在一个源文件中多次使用了 #include “myheader.h”,那么这个头文件就会被多次包含。
如果一个头文件被多次包含,就会出现重复定义的问题,导致编译错误。为了避免这种问题,通常会在头文件的开头加上条件编译指令,例如 #ifndef 和 #define,来防止头文件的重复包含。这样可以保证头文件只会被包含一次,避免重复定义的问题。

MYCLASS_H 是一个头文件的命名规则,通常用于防止同一文件中同一头文件的重复包含。这个命名规则是由以下步骤组成的:

将头文件名转换为大写字母形式,例如 myclass.h 变为 MYCLASS.H。

在文件名前面加上一个下划线,例如 MYCLASS.H 变为 _MYCLASS.H。

在文件名后面加上一个宏定义,例如 _MYCLASS.H 变为 _MYCLASS_H。

这个命名规则可以有效避免同一头文件在同一文件中的重复包含,同时也可以使头文件的名字更加清晰易懂。当然,这个命名规则并不是强制性的,你可以根据自己的需求来命名头文件。

_MYCLASS.H,_MYCLASS_H等的头文件的命名规则可以有很多种,但是只要在同一项目中保持一致即可。这样可以避免同名头文件的冲突,也方便项目的维护和管理。通常情况下,团队中的成员会约定一套命名规则,然后在整个项目中统一使用。这样可以让代码更加规范化和易于维护。

MyClass.cpp:

MyClass.cpp:
#include "MyClass.h"
#include <iostream>

void MyClass::setData(int shuru) {
    privateData = shuru; // 类内使用私有成员:将参数赋值给私有成员
    protectedData = shuru * 2; // 类内使用保护成员:将参数的两倍赋值给保护成员
    std::cout << "setData: privateData = " << privateData << ", protectedData = " << protectedData << std::endl;
    // 在类的成员函数中访问保护成员和私有成员
    privateFunc();
    protectedFunc();
}

int MyClass::getData() {
    return privateData; // 类内使用私有成员::直接使用私有成员的名称
}

void MyClass::protectedFunc() {
    std::cout << "This is a protected function." << std::endl;
} // 保护成员函数

void MyClass::privateFunc() {
    std::cout << "This is a private function." << std::endl;
} // 私有成员函数
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

main.cpp:

main.cpp:
#include <iostream>
#include "MyClass.h"

int main() {
    MyClass obj;
    obj.setData(1234);
    std::cout << "Data: " << obj.getData() << std::endl;

    // obj.privateData = 456; // 私有成员不能在类的外部访问
    // obj.privateFunc(); // 私有函数不能在类的外部访问
    // obj.protectedData = 789; // 不能在类的外部访问保护成员
    // obj.protectedFunc(); // 保护函数不能在类的外部访问

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

这样做的好处有很多:
1)使代码更加清晰和易于维护。类的界面和实现分开,可以更加清晰地区分哪些是公共接口,哪些是实现细节,方便用户和开发人员理解和使用。
2)提高代码的可重用性。将类的实现与界面分离,可以方便地将类的实现部分复用到其他项目中,而不必担心可能引入的冲突和问题。
3)增加代码的安全性。将类的实现细节隐藏起来,可以防止外部代码直接访问类的私有数据和私有方法,从而提高代码的安全性。
4)降低代码的耦合度。类的界面和实现分离,可以使类的实现部分与其他代码解耦,从而降低代码的耦合度,提高代码的可维护性和可扩展性。

在终端里可以使用下面指令执行:将MyClass.cpp文件编译成目标文件,并将其与main.cpp文件一起链接成可执行文件,才能正确运行程序。

g++ -c MyClass.cpp -o MyClass.o 
g++ main.cpp MyClass.o -o main
./main.exe
  • 1
  • 2
  • 3

当然这个算比较简单的了,复杂一些需要上Cmake

1.3、构造函数,析构函数,拷贝构造函数

构造函数和析构函数是 C++ 中特殊的成员函数,它们分别在对象构造和析构时被调用。
构造函数的主要作用是初始化对象的成员变量,为对象提供一个合理的初始状态。当我们创建一个对象时,编译器会自动调用对象的构造函数。如果我们没有定义构造函数,编译器会生成一个默认构造函数,它不做任何事情。但如果我们需要在对象创建时进行一些初始化操作,就需要定义自己的构造函数。
析构函数的主要作用是在对象销毁时释放资源,例如关闭文件、释放内存等。当对象被销毁时,编译器会自动调用对象的析构函数。如果我们没有定义析构函数,编译器会生成一个默认析构函数,它不做任何事情。但如果我们需要在对象销毁时进行一些清理操作,就需要定义自己的析构函数。
总之,构造函数和析构函数是 C++ 中非常重要的特殊成员函数,它们分别在对象构造和析构时被调用,用于初始化对象的成员变量和释放对象占用的资源。

例1.1加入构造函数和析构函数,得到例1.2,可以感受一下构造函数和析构函数的作用:

#include <iostream>
using namespace std;

class MyClass {
public:
    MyClass(int data) : privateData(data) {
        protectedData = data * 2;//初始化变量
        cout << "MyClass constructor called." << endl;
    }

    ~MyClass() {
        cout << "MyClass destructor called." << endl;
    }

    void setData(int shuru) {
        privateData = shuru;
        protectedData = shuru * 2;
        cout << "setData: privateData = " << privateData << ", protectedData = " << protectedData << endl;
        privateFunc();
        protectedFunc();
    }

    int getData() {
        return privateData;
    }

protected:
    int protectedData;
    void protectedFunc() {
        cout << "This is a protected function." << endl;
    }

private:
    int privateData;
    void privateFunc() {
        cout << "This is a private function." << endl;
    }
};

int main() {
    MyClass obj(100);
    obj.setData(12345);
    cout << "Data: " << obj.getData() << endl;

    return 0;
}

  • 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
  • 44
  • 45
  • 46
  • 47

这是一个构造函数的初始化列表,用于初始化私有成员变量privateData。privateData(data)表示将传入的参数data赋值给私有成员变量privateData。这种方式比在构造函数的函数体中直接赋值更高效,因为它避免了在构造函数体中再次对成员变量进行初始化。

当然一个类中,构造函数的形态不止一种,下面是一个例子:

#include <iostream>
using namespace std;

class MyClass {
public:
    MyClass() : privateData(0), protectedData(0) {
        cout << "MyClass default constructor called." << endl;
    }

    MyClass(int data) : privateData(data) {
        protectedData = data * 2;
        cout << "MyClass constructor with one parameter called." << endl;
    }

    MyClass(int data1, int data2) : privateData(data1), protectedData(data2) {
        cout << "MyClass constructor with two parameters called." << endl;
    }

    ~MyClass() {
        cout << "MyClass destructor called." << endl;
    }

    void setData(int shuru) {
        privateData = shuru;
        protectedData = shuru * 2;
        cout << "setData: privateData = " << privateData << ", protectedData = " << protectedData << endl;
        privateFunc();
        protectedFunc();
    }

    int getData() {
        return privateData;
    }

protected:
    int protectedData;
    void protectedFunc() {
        cout << "This is a protected function." << endl;
    }

private:
    int privateData;
    void privateFunc() {
        cout << "This is a private function." << endl;
    }
};

int main() {
    MyClass obj1;
    MyClass obj2(100);
    MyClass obj3(100, 200);
    obj2.setData(12345);
    cout << "Data: " << obj2.getData() << endl;

    return 0;
}

  • 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
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

这个代码中,我们添加了一个默认构造函数MyClass(),一个带有一个参数的构造函数MyClass(int data),和一个带有两个参数的构造函数MyClass(int data1, int data2)。同时,我们也修改了构造函数的输出信息,以便更好地区分它们。
输出结果:
在这里插入图片描述

构造函数和析构函数的关系:

构造函数和析构函数是成对出现的,构造函数用于初始化对象,而析构函数用于清理对象。在对象创建时,构造函数会被调用一次,而在对象销毁时,析构函数会被调用一次。因此,它们是一一对应的。
在程序中,构造函数和析构函数的调用次数取决于对象的创建和销毁。如果只创建了一个对象,那么构造函数和析构函数各被调用一次。如果创建了多个对象,那么每个对象都会调用一次构造函数和析构函数。如果对象是在栈上创建的,那么它们的构造函数和析构函数会在进入和离开作用域时被调用。如果对象是在堆上创建的,那么需要手动调用delete来销毁对象,这时析构函数会被调用。
需要注意的是,如果一个类继承了另一个类,那么在创建和销毁对象时,构造函数和析构函数的调用顺序是从基类到派生类的。也就是说,先调用基类的构造函数,再调用派生类的构造函数;先调用派生类的析构函数,再调用基类的析构函数。

除此之外,还有一种特殊的构造函数:拷贝构造函数
拷贝构造函数是一种特殊的构造函数,用于将一个对象的值复制到另一个对象中。拷贝构造函数的作用是创建一个新对象,并将已有对象的值复制到新对象中。它常用于以下三种情况:
1)用一个已有对象来初始化一个新对象。
2)将一个对象作为参数传递给一个函数。
3)在函数中返回一个对象。
举个例子:

#include <iostream>
using namespace std;

class MyClass {
public:
    MyClass() : privateData(0), protectedData(0) {
        cout << "MyClass default constructor called." << endl;
    }

    MyClass(int data) : privateData(data) {
        protectedData = data * 2;
        cout << "MyClass constructor with one parameter called." << endl;
    }

    MyClass(int data1, int data2) : privateData(data1), protectedData(data2) {
        cout << "MyClass constructor with two parameters called." << endl;
    }

    MyClass(const MyClass& other) {
        privateData = other.privateData;
        protectedData = other.protectedData;
        cout << "MyClass copy constructor called." << endl;
    }

    ~MyClass() {
        cout << "MyClass destructor called." << endl;
    }

    void setData(int shuru) {
        privateData = shuru;
        protectedData = shuru * 2;
        cout << "setData: privateData = " << privateData << ", protectedData = " << protectedData << endl;
        privateFunc();
        protectedFunc();
    }
       int getData() {
        return privateData;
    }

protected:
    int protectedData;
    void protectedFunc() {
        cout << "This is a protected function." << endl;
    }

private:
    int privateData;
    void privateFunc() {
        cout << "This is a private function." << endl;
    }
};

void func1(MyClass obj) {
    cout << "func1 called." << endl;
}

MyClass func2() {
    cout << "func2 called." << endl;
    MyClass obj(456);
    return obj;
}

int main() {
    MyClass obj1;
    MyClass obj2(100);
    MyClass obj3(100, 200);
    obj2.setData(12345);
    cout << "Data: " << obj2.getData() << endl;

    MyClass obj4(obj2);
    MyClass obj5 = obj3;

    func1(obj4);
    MyClass obj6 = func2();

    return 0;
}
  • 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
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77

这里添加了一个拷贝构造函数,用于将一个对象的值复制到另一个对象中。同时,我添加了两个函数,一个是将对象作为参数传递给函数,另一个是在函数中返回对象。这两个函数都会自动调用拷贝构造函数。
在 main 函数中,我创建了几个对象,并调用了它们的成员函数。然后,我创建了两个新的对象,一个是通过拷贝构造函数创建的,另一个是通过赋值运算符创建的。最后,我调用了两个函数,一个是将对象作为参数传递给函数,另一个是在函数中返回对象。在这两个函数中,都会自动调用拷贝构造函数。

在这里插入图片描述

在这些情况下,如果没有定义拷贝构造函数,编译器会自动生成一个默认的拷贝构造函数来完成对象的复制。如果需要进行深拷贝,需要自己定义拷贝构造函数。

1.4、静态数据成员和静态成员函数

静态数据成员是指属于类的成员,而不是属于类的对象的成员。它们是在类被定义时被声明的,而不是在类的对象被创建时被定义的。它们被所有该类的对象所共享,因此它们的值在所有对象之间都是相同的。静态数据成员在程序开始时候被创建,在程序结束时被销毁(而不是构造和析构)

静态数据成员的存在有以下几个好处:
与普通成员变量不同,静态数据成员不需要在每个对象中都存储一份。这可以节省内存空间。
静态数据成员可以被所有该类的对象所共享,这使得它们可以用于在类的所有对象之间传递信息。
静态数据成员可以在类的外部被访问,这使得它们可以用于实现类似于全局变量的功能。
静态数据成员可以被用作常量表达式,这使得它们可以用于在编译时计算常量值。

静态成员函数是指在类中使用 static 关键字修饰的成员函数。静态成员函数不依赖于任何对象,可以直接通过类名调用,而不需要创建对象。因此,静态成员函数不能访问非静态成员变量和非静态成员函数,只能访问静态成员变量和静态成员函数。

(示例):

#include <iostream>
using namespace std;

class MyClass {
public:
    MyClass() : privateData(0), protectedData(0) {
        cout << "MyClass default constructor called." << endl;
        count++; // 每次创建对象时,对象数量加1
    }

    MyClass(int data) : privateData(data) {
        protectedData = data * 2;
        cout << "MyClass constructor with one parameter called." << endl;
        count++; // 每次创建对象时,对象数量加1
    }

    MyClass(int data1, int data2) : privateData(data1), protectedData(data2) {
        cout << "MyClass constructor with two parameters called." << endl;
        count++; // 每次创建对象时,对象数量加1
    }

    MyClass(const MyClass& other) {
        privateData = other.privateData;
        protectedData = other.protectedData;
        cout << "MyClass copy constructor called." << endl;
        count++; // 每次创建对象时,对象数量加1
    }

    ~MyClass() {
        cout << "MyClass destructor called." << endl;
        count--; // 每次销毁对象时,对象数量减1
    }

    void setData(int shuru) {
        privateData = shuru;
        protectedData = shuru * 2;
        cout << "setData: privateData = " << privateData << ", protectedData = " << protectedData << endl;
        privateFunc();
        protectedFunc();
    }
    
    int getData() {
        return privateData;
    }

    static int getCount() {
        return count; // 返回对象数量
    }

protected:
    int protectedData;
    void protectedFunc() {
        cout << "This is a protected function." << endl;
    }

private:
    int privateData;
    void privateFunc() {
        cout << "This is a private function." << endl;
    }

    static int count; // 所有对象的数量
};

int MyClass::count = 0; // 在类外部初始化静态数据成员

void func1(MyClass obj) {
    cout << "func1 called." << endl;
}

MyClass func2() {
    cout << "func2 called." << endl;
    MyClass obj(456);
    return obj;
}

int main() {
    MyClass obj1;
    MyClass obj2(100);
    MyClass obj3(100, 200);
    obj2.setData(12345);
    cout << "Data: " << obj2.getData() << endl;

    MyClass obj4(obj2);
    MyClass obj5 = obj3;

    cout << "Total objects: " << MyClass::getCount() << endl; // 输出对象数量

    func1(obj4);
    MyClass obj6 = func2();

    cout << "Total objects: " << MyClass::getCount() << endl; // 输出对象数量

    return 0;
}
  • 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
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95

静态成员经常用来做计数器的功能,就像上面代码所示。
当然静态数据成员可以被对象引用,比如MyClass::getCount()其实和obj6.getCount()实现的效果差不多,但是:
MyClass::getCount() 是静态调用,不需要创建对象,可以在任何地方调用;

obj6.getCount() 是动态调用,需要先创建对象,然后通过对象名调用。

另外需要注意的是,静态成员函数只能访问静态成员变量和静态成员函数,而不能访问非静态成员变量和非静态成员函数。因此,如果需要访问非静态成员变量或非静态成员函数,就需要通过对象名调用成员函数。

1.5、友元函数,友元类

友元是C++语言中的一个特殊概念,它允许某个函数或类访问另一个类的保护成员和私有成员。C++中的封装性要求类的私有成员和保护成员只能被类的成员函数访问,而友元机制则打破了这个限制,允许某些外部函数或类访问私有成员和保护成员,从而提高了程序的灵活性和可扩展性。

友元函数可以放在类中的任意位置,实现部分放在类外。

友元类是指一个类可以访问另一个类的私有成员和保护成员,这个类就是另一个类的友元类。友元类的声明方式和友元函数类似,需要在类的声明中使用friend关键字进行声明。
友元类可以访问被它所声明的类的私有成员和保护成员,但是它本身并不是被它所声明的类的成员,因此它不能直接访问被它所声明的类的成员变量和成员函数。如果需要访问被它所声明的类的成员变量和成员函数,可以通过类对象或者指针来访问。

下面是一个例子:

#include <iostream>
using namespace std;

class MyClass {
public:
    MyClass() : privateData(0), protectedData(0) {
        cout << "MyClass default constructor called." << endl;
        count++; // 每次创建对象时,对象数量加1
    }

    MyClass(int data) : privateData(data) {
        protectedData = data * 2;
        cout << "MyClass constructor with one parameter called." << endl;
        count++; // 每次创建对象时,对象数量加1
    }

    MyClass(int data1, int data2) : privateData(data1), protectedData(data2) {
        cout << "MyClass constructor with two parameters called." << endl;
        count++; // 每次创建对象时,对象数量加1
    }

    MyClass(const MyClass& other) {
        privateData = other.privateData;
        protectedData = other.protectedData;
        cout << "MyClass copy constructor called." << endl;
        count++; // 每次创建对象时,对象数量加1
    }

    ~MyClass() {
        cout << "MyClass destructor called." << endl;
        count--; // 每次销毁对象时,对象数量减1
    }

    void setData(int shuru) {
        privateData = shuru;
        protectedData = shuru * 2;
        cout << "setData: privateData = " << privateData << ", protectedData = " << protectedData << endl;
        privateFunc();
        protectedFunc();
    }
    
    int getData() {
        return privateData;
    }

    static int getCount() {
        return count; // 返回对象数量
    }

protected:
    int protectedData;
    void protectedFunc() {
        cout << "This is a protected function." << endl;
    }

private:
    int privateData;
    void privateFunc() {
        cout << "This is a private function." << endl;
    }

    static int count; // 所有对象的数量

    friend void friendFunc(MyClass obj); // 声明友元函数
    friend class FriendClass; // 声明友元类,FriendClass是Myclass的友元类
};

int MyClass::count = 0; // 在类外部初始化静态数据成员

void friendFunc(MyClass obj) { // 定义友元函数
    cout << "friendFunc called, privateData = " << obj.privateData << endl;
    obj.privateFunc();
}

class FriendClass { // 定义友元类
public:
    void friendClassFunc(MyClass obj) {
        cout << "FriendClass called, protectedData = " << obj.protectedData << endl;
        obj.protectedFunc();
    }
};

void func1(MyClass obj) {
    cout << "func1 called." << endl;
    friendFunc(obj); // 调用友元函数
}
MyClass func2() {
    cout << "func2 called." << endl;
    MyClass obj(456);
    return obj;
}
int main() {
    MyClass obj1;
    MyClass obj2(100);
    MyClass obj3(100, 200);
    obj2.setData(12345);
    cout << "Data: " << obj2.getData() << endl;
    MyClass obj4(obj2);
    MyClass obj5 = obj3;

    cout << "Total objects: " << MyClass::getCount() << endl; // 输出对象数量

    func1(obj4);

    FriendClass friendObj; // 创建友元类对象
    friendObj.friendClassFunc(obj2); // 调用友元类的成员函数

    MyClass obj6 = func2();

    cout << "Total objects: " << MyClass::getCount() << endl; // 输出对象数量

    return 0;

}
  • 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
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114

友元的弊端主要有以下几点:
破坏了封装性:友元可以访问类的私有成员和保护成员,这破坏了封装性,可能会导致类的安全性降低。

可能会导致耦合度增加:友元函数或友元类需要知道类的实现细节,这可能会导致类和友元之间的耦合度增加,使得类的实现变得更加复杂。

友元不具有继承性:如果一个类是另一个类的友元,那么它并不具有继承性,即它不能访问派生类的私有成员和保护成员。
因此,在使用友元时需要慎重考虑,只有在必要的情况下才应该使用友元。

二、类的实现——对象

2.1、对象的静态分配,动态分配(堆对象)

对象内存的静态分配和动态分配是指在程序运行时,对象所占用的内存空间是在编译时确定还是在运行时动态分配的。
静态分配是指在编译时为对象分配固定大小的内存空间,这种方式也称为栈分配。栈是一种先进后出的数据结构,它的内存分配和释放是由系统自动完成的,程序员不需要手动管理内存。静态分配的优点是速度快,缺点是内存空间固定,不够灵活,无法动态扩展。
动态分配是指在程序运行时根据需要动态地分配内存空间,这种方式也称为堆分配。堆是一种动态分配内存的方式,程序员需要手动管理内存的分配和释放。动态分配的优点是灵活性强,可以动态地扩展内存空间,缺点是速度较慢,容易出现内存泄漏和内存碎片等问题。
对于对象的内存分配,一般情况下建议使用动态分配,尤其是对象的大小不确定或者需要动态扩展时。但是在某些情况下,静态分配也是一种不错的选择,例如对象的大小固定且较小,或者需要频繁创建和销毁对象时,使用静态分配可以提高程序的运行效率。

静态分配的对象也称为自动变量(automatic variable)、栈变量(stack variable)或局部变量(local variable)。它们的内存在程序编译时就已经分配好了,作用域只在当前代码块中,当代码块执行完毕时,它们会自动被销毁。
动态分配的对象也称为堆对象(heap object)、动态对象(dynamic object)或者动态内存分配对象(dynamically allocated object)。它们的内存空间在程序运行时动态分配,作用域可以跨越多个代码块,需要手动管理内存的分配和释放。

下面是堆对象建立删除,堆对象数组建立删除的例子

#include <iostream>
using namespace std;

class MyClass
{
public:
    MyClass() { cout << "MyClass constructed!" << endl; }
    ~MyClass() { cout << "MyClass destructed!" << endl; }
};

int main()
{
    // 建立堆对象
    MyClass* ptr = new MyClass();
    // 使用堆对象
    cout << "Using heap object..." << endl;
    // 删除堆对象
    delete ptr;
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
#include <iostream>
using namespace std;

class MyClass
{
public:
    MyClass() { cout << "MyClass constructed!" << endl; }
    ~MyClass() { cout << "MyClass destructed!" << endl; }
};

int main()
{
    // 建立堆对象数组
    MyClass* arr = new MyClass[5];
    // 使用堆对象数组
    cout << "Using heap object array..." << endl;
    // 删除堆对象数组
    delete[] arr;
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

2.2、子对象

2.3、this指针

this指针是一个指向当前对象的指针,它在成员函数中使用。this指针的作用是为了区分同名的成员变量和局部变量。在成员函数中,如果有一个参数与成员变量同名,那么在函数中直接使用该变量名时,编译器会默认使用参数而不是成员变量。这时可以使用this指针来明确指出要使用的是成员变量而不是参数。
另外,this指针还可以用于在一个成员函数中返回当前对象的引用。例如,可以在一个成员函数中返回*this,表示返回当前对象的引用。这在链式调用中非常常见,可以使代码更加简洁易读。

举个例子:

#include <iostream>
using namespace std;

class Person {
public:
    int age;
    int getAge() {
        return this->age;
    }
};

int main() {
    Person person1;
    person1.age = 18;
    cout << "person1's age is " << person1.getAge() << endl;

    Person person2;
    person2.age = 25;
    cout << "person2's age is " << person2.getAge() << endl;

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

上面等价于下面:

```cpp
#include <iostream>
using namespace std;

class Person {
public:
    int age;
    int getAge() {
        return age;
    }
};

int main() {
    Person person1;
    person1.age = 18;
    cout << "person1's age is " << person1.getAge() << endl;

    Person person2;
    person2.age = 25;
    cout << "person2's age is " << person2.getAge() << endl;

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

在成员函数中,可以直接访问类的成员变量,不需要使用"this"指针。因此,"return this->age"和"return age"是等价的。不过,使用"this"指针可以明确地表示当前对象,增加代码的可读性。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/233707?site
推荐阅读
相关标签
  

闽ICP备14008679号