剖析【C++】——类和对象(下篇)——超详解——小白篇

2024-06-15 10:19:06 浏览数 (2)

1.再谈构造函数

1.1 构造函数体赋值

构造函数是用来初始化对象的特殊函数。当我们创建一个对象时,编译器会自动调用构造函数来给对象的成员变量赋值。

简单理解:当你买一个新手机,打开包装,这就像调用了构造函数。手机里的默认应用程序就像成员变量的初始值。这些默认应用虽然是预装的,但我们可以再安装新的应用,这相当于构造函数体内的赋值操作。

示例代码:

代码语言:javascript复制
class MyClass {
public:
    int x;
    int y;
    MyClass(int a, int b) : x(a), y(b) {}  // 初始化列表
};

在上面的代码中,当我们创建一个 MyClass 对象时,比如 MyClass obj(10);,构造函数 MyClass(int value) 会被调用,x 会被赋值为 10。但是这个过程叫赋值,不叫初始化,因为我们可以在构造函数内多次赋值。

总结:构造函数体内的赋值可以多次进行,而初始化只能进行一次。

1.2 初始化列表

初始化列表 是另一种在创建对象时给成员变量赋初值的方法。这种方法更高效,因为它在对象创建时就直接赋值,而不是先创建对象然后再赋值。

简单理解:假设你买了一辆新车,初始化列表就像你在购车前已经决定好车的颜色、型号等,而不是买车后再去喷漆改装。

示例代码:

代码语言:javascript复制
class MyClass {
public:
    int x;
    int y;
    MyClass(int a, int b) : x(a), y(b) {}  // 初始化列表
};

在上面的代码中,: x(a), y(b) 就是初始化列表,它在构造函数体执行之前完成成员变量的初始化。

注意事项

  1. 每个成员变量在初始化列表中只能出现一次。
  2. 以下成员必须在初始化列表中初始化:
    • 引用类型成员变量
    • const 成员变量
    • 自定义类型成员变量(如果该类没有默认构造函数)

示例代码:

代码语言:javascript复制
class MyClass {
public:
    const int x;
    int &y;
    MyClass(int a, int &b) : x(a), y(b) {}  // 必须在初始化列表中初始化
};

在这段代码中,const int xint &y 必须在初始化列表中进行初始化,因为 const 成员变量和引用类型成员变量在对象创建时就需要确定其初始值。

总结:尽量使用初始化列表,因为它对自定义类型成员变量更高效。

1.3 explicit 关键字

构造函数不仅可以用来创建对象,还可以用来进行类型转换。对于只有一个参数的构造函数,如果不使用 explicit 关键字,编译器会自动进行隐式类型转换。

简单理解:假设有一个银行系统,你有一个账户类。如果允许隐式转换,就像允许陌生人随意把你的银行账号转换成他们的账户,这很危险。所以我们需要 explicit 关键字来禁止这种转换。

示例代码:

代码语言:javascript复制
class MyClass {
public:
    int x;
    explicit MyClass(int value) : x(value) {}  // 使用 explicit 关键字
};

void func(MyClass obj) {
    // do something
}

int main() {
    MyClass obj1(10);  // 正常调用构造函数
    // MyClass obj2 = 10;  // 错误,explicit 禁止隐式转换
    // func(20);  // 错误,explicit 禁止隐式转换
    return 0;
}

在上面的代码中,explicit 关键字禁止了构造函数的隐式转换,这样可以避免潜在的错误和提高代码的可读性。

总结:使用 explicit 关键字可以防止构造函数被用于隐式转换,确保代码的安全性和可读性。

2. Static成员

2.1 概念

在C 中,声明为static的类成员称为类的静态成员。静态成员分为静态成员变量和静态成员函数。

  • 静态成员变量:用static修饰的成员变量。
  • 静态成员函数:用static修饰的成员函数。

简单理解:静态成员就像学校里的公共设施,比如学校的大门(静态成员变量)和学校的公告栏(静态成员函数),它们是所有学生(类的对象)共享的,而不是某个学生独有的。

初始化:静态成员变量必须在类外进行初始化。

面试题:实现一个类,计算程序中创建了多少个类对象。

示例代码

代码语言:javascript复制
#include <iostream>
using namespace std;

class MyClass {
public:
    static int count;  // 静态成员变量声明

    MyClass() {
        count  ;  // 每创建一个对象,count加1
    }

    // 静态成员函数
    static int getCount() {
        return count;
    }
};

// 静态成员变量在类外定义并初始化
int MyClass::count = 0;

int main() {
    MyClass obj1;
    MyClass obj2;
    MyClass obj3;
    cout << "Created objects: " << MyClass::getCount() << endl;
    return 0;
}

在这段代码中,MyClass类的静态成员变量count记录了创建的对象数量,每创建一个对象,count就加1。静态成员函数getCount返回当前的对象数量。

2.2 特性

  1. 静态成员为所有类对象共享:静态成员变量存放在静态区,不属于某个具体的对象。
  2. 静态成员变量必须在类外定义:定义时不添加static关键字,类中只是声明。
  3. 访问静态成员:静态成员可以通过类名::静态成员对象.静态成员访问。
  4. 静态成员函数没有this指针:不能访问任何非静态成员。
  5. 静态成员受访问限定符限制:静态成员同样受publicprotectedprivate访问限定符的限制。

示例代码

代码语言:javascript复制
class MyClass {
public:
    static int staticVar;  // 静态成员变量声明
    int nonStaticVar;      // 非静态成员变量

    static void staticFunc() {
        // 静态成员函数不能访问非静态成员
        // cout << nonStaticVar;  // 错误
        cout << staticVar;  // 正确
    }

    void nonStaticFunc() {
        cout << staticVar;  // 非静态成员函数可以访问静态成员
        cout << nonStaticVar;  // 非静态成员函数可以访问非静态成员
    }
};

// 静态成员变量定义
int MyClass::staticVar = 0;

问题解答

1.静态成员函数可以调用非静态成员函数吗? 不可以。静态成员函数没有this指针,所以不能访问类的非静态成员(包括非静态成员函数)。

示例代码

代码语言:javascript复制
class MyClass {
public:
    static void staticFunc() {
        // nonStaticFunc();  // 错误,静态成员函数不能调用非静态成员函数
    }

    void nonStaticFunc() {
        cout << "Non-static member function called." << endl;
    }
};

2.非静态成员函数可以调用类的静态成员函数吗? 可以。非静态成员函数可以访问类的所有成员,包括静态成员。

示例代码

代码语言:javascript复制
class MyClass {
public:
    static void staticFunc() {
        cout << "Static member function called." << endl;
    }

    void nonStaticFunc() {
        staticFunc();  // 非静态成员函数可以调用静态成员函数
    }
};

3. 友元

友元提供了一种特殊的机制,可以让一个类允许另一个类或函数访问其私有或受保护的成员。虽然友元增加了便利性,但也会破坏封装性,增加代码耦合度,因此使用时需谨慎。

3.1 友元函数

问题描述:在重载 operator<< 时,无法将其重载为成员函数,因为 cout 作为输出流对象和 this 指针会竞争第一个参数的位置。为了使 cout 成为第一个参数,我们需要将 operator<< 重载为全局函数。但全局函数不能直接访问类的私有成员,这时就需要友元函数来解决这个问题。

友元函数的特点

  1. 可以访问类的私有和保护成员,但不属于类的成员函数。
  2. 不能用 const 修饰。
  3. 可以在类定义的任何地方声明,不受类访问限定符限制。
  4. 一个函数可以是多个类的友元函数。
  5. 友元函数的调用与普通函数的调用相同。

示例代码

代码语言:javascript复制
#include <iostream>
using namespace std;

class MyClass {
private:
    int value;

public:
    MyClass(int v) : value(v) {}

    // 声明友元函数
    friend ostream& operator<<(ostream& os, const MyClass& obj);
};

// 定义友元函数
ostream& operator<<(ostream& os, const MyClass& obj) {
    os << obj.value;
    return os;
}

int main() {
    MyClass obj(42);
    cout << obj << endl;  // 输出: 42
    return 0;
}

在上面的代码中,operator<< 被定义为友元函数,因此它可以访问 MyClass 类的私有成员 value

3.2 友元类

友元类是一种类的所有成员函数都可以访问另一个类的私有和保护成员的机制。

特点

  1. 友元关系是单向的,不具有交换性。例如,如果 A 类是 B 类的友元,那么 B 类可以访问 A 类的私有成员,但反过来 A 类不能访问 B 类的私有成员。
  2. 友元关系不能传递。如果 BA 的友元,而 CB 的友元,这并不意味着 CA 的友元。
  3. 友元关系不能继承。

示例代码

代码语言:javascript复制
#include <iostream>
using namespace std;

class Date;

class Time {
private:
    int hour;
    int minute;

public:
    Time(int h, int m) : hour(h), minute(m) {}

    // 声明Date类为友元类
    friend class Date;
};

class Date {
private:
    int day;
    int month;
    int year;

public:
    Date(int d, int m, int y) : day(d), month(m), year(y) {}

    void displayTime(const Time& t) {
        // 访问Time类的私有成员
        cout << "Time: " << t.hour << ":" << t.minute << endl;
    }
};

int main() {
    Time t(10, 30);
    Date d(1, 5, 2023);
    d.displayTime(t);  // 输出: Time: 10:30
    return 0;
}

在上面的代码中,Date 类被声明为 Time 类的友元类,因此 Date 类的成员函数可以访问 Time 类的私有成员。

3.3总结

  • 友元函数和友元类允许访问私有和保护成员,但要谨慎使用,因为这会增加代码的耦合性。
  • 友元关系是单向的,不可传递。
  • 使用友元可以解决一些特殊情况下的访问权限问题,如重载运算符等。

4. 内部类

1.概念

内部类 是指定义在另一个类内部的类。它是一个独立的类,不属于外部类,不能通过外部类的对象访问其成员。

简单理解:就像一家大公司的部门(内部类)和公司(外部类),部门是独立的,但仍然是公司的一部分,外部类对内部类没有特别的访问权限。

注意事项

  • 内部类就像是外部类的友元类,内部类可以通过外部类的对象参数访问外部类的所有成员。
  • 外部类对内部类没有友元访问权限,不能访问内部类的私有成员。

2.特性

  1. 内部类的位置:内部类可以定义在外部类的 publicprotectedprivate 区域。
  2. 访问外部类的静态成员:内部类可以直接访问外部类的静态成员,而不需要外部类的对象或类名。
  3. 大小计算sizeof(外部类) 只计算外部类的大小,与内部类无关。
示例代码:
代码语言:javascript复制
#include <iostream>
using namespace std;

class OuterClass {
private:
    int outerVar;
    static int outerStaticVar;

public:
    OuterClass(int val) : outerVar(val) {}

    // 定义内部类
    class InnerClass {
    public:
        void displayOuter(OuterClass& outer) {
            // 访问外部类的非静态成员需要外部类的对象
            cout << "Outer class non-static member: " << outer.outerVar << endl;
        }

        void displayOuterStatic() {
            // 直接访问外部类的静态成员
            cout << "Outer class static member: " << outerStaticVar << endl;
        }
    };

    // 外部类中的成员函数可以创建内部类的对象
    void createInner() {
        InnerClass inner;
        inner.displayOuter(*this);
        inner.displayOuterStatic();
    }
};

// 定义并初始化外部类的静态成员
int OuterClass::outerStaticVar = 10;

int main() {
    OuterClass outer(5);
    outer.createInner();

    // 外部类对象不能直接访问内部类成员
    // outer.InnerClass inner;  // 错误

    // 创建内部类对象
    OuterClass::InnerClass inner;
    inner.displayOuter(outer);
    inner.displayOuterStatic();

    return 0;
}
代码分析
  1. 内部类的位置InnerClass 定义在 OuterClasspublic 区域内,可以访问外部类的静态和非静态成员。
  2. 访问外部类的静态成员InnerClass 中的 displayOuterStatic 函数直接访问 OuterClass 的静态成员 outerStaticVar
  3. 访问外部类的非静态成员displayOuter 函数通过 OuterClass 的对象参数 outer 访问其非静态成员 outerVar

3.总结

  • 内部类 是独立的类,可以定义在外部类的任何访问区域。
  • 内部类可以直接访问外部类的静态成员,不需要外部类的对象或类名。
  • 内部类可以通过外部类的对象参数访问外部类的非静态成员。
  • 外部类不能访问内部类的私有成员,内部类也不会影响外部类的大小计算。

5.再次理解类和对象

1. 抽象现实生活中的实体

在现实生活中,计算机无法直接认识物理世界中的实体,如洗衣机。为了让计算机理解这些实体,我们需要通过面向对象的语言(如C )对它们进行抽象和描述。

简单理解:假设你想让计算机认识洗衣机。首先,你需要在头脑中抽象出洗衣机的属性和功能。比如,洗衣机有颜色、品牌、容量等属性,还有启动、停止、洗涤等功能。

2. 用类描述实体

一旦你在人为思想层面对洗衣机有了清晰的认识,就需要用某种编程语言(如C )将这种认识转化为计算机能理解的格式。我们使用“类”来描述洗衣机。

示例代码

代码语言:javascript复制
class WashingMachine {
public:
    // 属性
    string color;
    string brand;
    int capacity;

    // 方法
    void start() {
        cout << "Washing machine started." << endl;
    }

    void stop() {
        cout << "Washing machine stopped." << endl;
    }

    void wash() {
        cout << "Washing machine is washing clothes." << endl;
    }
};

在上面的代码中,我们定义了一个类 WashingMachine,它包含了洗衣机的属性(颜色、品牌、容量)和方法(启动、停止、洗涤)。

3. 实例化对象

定义了类之后,计算机还不能理解洗衣机是什么。我们需要通过类来实例化具体的洗衣机对象。

示例代码

代码语言:javascript复制
int main() {
    // 实例化一个洗衣机对象
    WashingMachine myWasher;

    // 给属性赋值
    myWasher.color = "White";
    myWasher.brand = "LG";
    myWasher.capacity = 7;

    // 调用方法
    myWasher.start();
    myWasher.wash();
    myWasher.stop();

    return 0;
}

在这段代码中,我们创建了一个 WashingMachine 对象 myWasher,并为其属性赋值,然后调用其方法来模拟洗衣机的行为。

4. 总结类和对象的关系

是对某一类实体的抽象和描述。类定义了这些实体具有的属性和方法,形成了一种新的自定义类型。

对象 是类的实例,是具体的实体。通过实例化类,我们创建对象,然后可以使用这些对象来模拟现实中的实体。

现实生活中的模拟

  • 抽象:你在人为思想层面对洗衣机进行认识,确定它的属性和功能。
  • :用C 类来描述洗衣机的属性和功能,将这种描述输入计算机中。
  • 实例化:通过类实例化具体的洗衣机对象,计算机才真正理解和模拟洗衣机的行为。

0 人点赞