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

C++ 类与实例化

2023-06-164.8k 阅读

C++ 类的基本概念

类的定义

在 C++ 中,类(class)是一种用户自定义的数据类型,它将数据成员(成员变量)和函数成员(成员函数)封装在一起。类的定义语法如下:

class ClassName {
    // 访问修饰符
    private:
        // 私有数据成员和成员函数
        int privateData;
        void privateFunction();
    public:
        // 公有数据成员和成员函数
        int publicData;
        void publicFunction();
    protected:
        // 保护数据成员和成员函数
        int protectedData;
        void protectedFunction();
};

在上述代码中,ClassName 是类的名称。类体中包含了不同访问修饰符下的数据成员和成员函数。

访问修饰符

  1. private:私有成员只能在类的内部被访问,类的外部和派生类都无法直接访问私有成员。这提供了数据的安全性和封装性,防止外部代码随意修改类的内部状态。
  2. public:公有成员可以在类的内部和外部被访问。通常将一些操作接口定义为公有成员,以便外部代码与类进行交互。
  3. protected:保护成员在类的内部和派生类中可以被访问,但在类的外部不能直接访问。它主要用于在继承体系中,让派生类能够访问基类的部分成员,同时又对外部代码保持一定的封装性。

成员变量与成员函数

  1. 成员变量:也称为数据成员,是类中定义的变量,用于存储对象的状态信息。例如,在一个 Person 类中,可以定义 nameage 等成员变量来描述一个人的姓名和年龄。
class Person {
private:
    std::string name;
    int age;
public:
    // 成员函数用于操作成员变量
    void setName(const std::string& n) {
        name = n;
    }
    void setAge(int a) {
        age = a;
    }
    std::string getName() const {
        return name;
    }
    int getAge() const {
        return age;
    }
};

在上述 Person 类中,nameage 是私有成员变量,通过公有成员函数 setNamesetAgegetNamegetAge 来对其进行访问和修改。

  1. 成员函数:是类中定义的函数,用于对成员变量进行操作,实现类的各种行为。成员函数可以访问类的所有成员,包括私有成员。成员函数的定义可以在类体内部,也可以在类体外部。在类体外部定义时,需要使用作用域解析运算符 :: 来指定函数所属的类。
// 在类体外部定义成员函数
void Person::printInfo() {
    std::cout << "Name: " << name << ", Age: " << age << std::endl;
}

类的实例化

实例化的概念

类只是一种数据类型的定义,它本身并不占用实际的内存空间。要使用类,需要创建类的实例,这个过程称为实例化。实例化后的对象是类的具体实现,占用实际的内存空间。例如,Person 类定义了人的属性和行为,而通过实例化 Person 类,可以创建具体的 person1person2 等对象,每个对象都有自己独立的 nameage 等成员变量的存储空间。

创建对象

  1. 栈上创建对象:在函数内部或代码块中,可以直接定义对象,对象存储在栈上。例如:
int main() {
    Person person;
    person.setName("Alice");
    person.setAge(25);
    person.printInfo();
    return 0;
}

在上述代码中,personPerson 类的一个对象,在 main 函数的栈上创建。对象创建时,会调用类的默认构造函数(如果存在)。

  1. 堆上创建对象:使用 new 关键字可以在堆上创建对象,返回一个指向该对象的指针。例如:
int main() {
    Person* personPtr = new Person();
    personPtr->setName("Bob");
    personPtr->setAge(30);
    personPtr->printInfo();
    delete personPtr;
    return 0;
}

在上述代码中,personPtr 是一个指向 Person 类对象的指针,通过 new 在堆上创建对象。使用完对象后,需要使用 delete 关键字释放内存,以避免内存泄漏。

构造函数与析构函数

  1. 构造函数:构造函数是一种特殊的成员函数,用于在对象创建时初始化对象的成员变量。构造函数的名称与类名相同,没有返回类型(包括 void)。例如:
class Person {
private:
    std::string name;
    int age;
public:
    // 构造函数
    Person(const std::string& n, int a) : name(n), age(a) {
        std::cout << "Person object created." << std::endl;
    }
    // 析构函数
    ~Person() {
        std::cout << "Person object destroyed." << std::endl;
    }
    void printInfo() {
        std::cout << "Name: " << name << ", Age: " << age << std::endl;
    }
};

在上述代码中,Person(const std::string& n, int a) : name(n), age(a) 是构造函数,它使用成员初始化列表对 nameage 进行初始化。成员初始化列表的效率通常比在构造函数体中赋值更高,特别是对于类类型的成员变量。

  1. 析构函数:析构函数也是一种特殊的成员函数,用于在对象销毁时执行清理工作,例如释放对象占用的动态分配的资源。析构函数的名称是在类名前加上波浪线 ~,同样没有返回类型。例如,上述代码中的 ~Person() 就是析构函数,当 Person 对象生命周期结束时,会自动调用析构函数。

拷贝构造函数与赋值运算符重载

  1. 拷贝构造函数:拷贝构造函数用于创建一个新对象,该对象是另一个已有对象的副本。拷贝构造函数的参数是同类对象的引用。例如:
class Person {
private:
    std::string name;
    int age;
public:
    Person(const std::string& n, int a) : name(n), age(a) {}
    // 拷贝构造函数
    Person(const Person& other) : name(other.name), age(other.age) {
        std::cout << "Copy constructor called." << std::endl;
    }
    // 赋值运算符重载
    Person& operator=(const Person& other) {
        if (this != &other) {
            name = other.name;
            age = other.age;
            std::cout << "Assignment operator called." << std::endl;
        }
        return *this;
    }
    void printInfo() {
        std::cout << "Name: " << name << ", Age: " << age << std::endl;
    }
};

在上述代码中,Person(const Person& other) 是拷贝构造函数,它通过成员初始化列表复制 other 对象的成员变量。当使用一个已有的 Person 对象来初始化另一个新的 Person 对象时,会调用拷贝构造函数。

  1. 赋值运算符重载:赋值运算符 = 用于将一个对象的值赋给另一个已存在的对象。通过重载赋值运算符,可以自定义对象之间的赋值行为。例如,上述代码中的 Person& operator=(const Person& other) 是赋值运算符的重载函数。在函数内部,首先检查是否是自我赋值(this != &other),然后复制成员变量的值,并返回 *this,以便支持链式赋值。

类的继承与多态

继承的概念

继承是面向对象编程中的一个重要特性,它允许一个类(派生类)从另一个类(基类)中获取成员变量和成员函数。通过继承,派生类可以复用基类的代码,并且可以添加自己特有的成员变量和成员函数。继承的语法如下:

class BaseClass {
public:
    int baseData;
    void baseFunction() {
        std::cout << "Base class function." << std::endl;
    }
};

class DerivedClass : public BaseClass {
public:
    int derivedData;
    void derivedFunction() {
        std::cout << "Derived class function." << std::endl;
    }
};

在上述代码中,DerivedClass 是派生类,BaseClass 是基类。DerivedClass 通过 public 关键字继承自 BaseClass,这意味着 BaseClass 的公有成员在 DerivedClass 中仍然是公有成员。

继承方式

  1. public 继承:在 public 继承中,基类的公有成员和保护成员在派生类中的访问权限不变,而基类的私有成员在派生类中仍然不可访问。例如,在上述代码中,DerivedClass 对象可以访问 BaseClass 的公有成员 baseDatabaseFunction
  2. private 继承:在 private 继承中,基类的公有成员和保护成员在派生类中都变为私有成员,基类的私有成员在派生类中仍然不可访问。这意味着通过 private 继承,派生类对基类成员进行了更严格的封装,外部代码和派生类的派生类都无法直接访问从基类继承的成员(除非通过派生类提供的接口)。
  3. protected 继承:在 protected 继承中,基类的公有成员在派生类中变为保护成员,基类的保护成员在派生类中的访问权限不变,基类的私有成员在派生类中仍然不可访问。这使得基类的公有成员在派生类及其派生类中可以访问,但对外部代码不可访问。

多态性

  1. 静态多态(函数重载与模板):静态多态是在编译时确定调用哪个函数,主要通过函数重载和模板实现。
    • 函数重载:在同一个作用域内,多个函数可以具有相同的名称,但参数列表不同(参数个数、类型或顺序不同)。编译器会根据调用函数时提供的实参类型和个数来选择合适的函数。例如:
class Calculator {
public:
    int add(int a, int b) {
        return a + b;
    }
    double add(double a, double b) {
        return a + b;
    }
};

在上述 Calculator 类中,有两个 add 函数,它们通过参数类型的不同进行重载。

- **模板**:模板允许编写通用的代码,能够适应不同的数据类型。函数模板和类模板是 C++ 实现静态多态的重要手段。例如:
template <typename T>
T add(T a, T b) {
    return a + b;
}

上述代码定义了一个函数模板 add,它可以对不同类型的数据进行加法运算,编译器会根据实际调用时的类型参数生成具体的函数实例。

  1. 动态多态(虚函数与运行时多态):动态多态是在运行时确定调用哪个函数,主要通过虚函数和指针或引用实现。
    • 虚函数:在基类中使用 virtual 关键字声明的函数称为虚函数。派生类可以重写(override)基类的虚函数,以提供自己的实现。例如:
class Shape {
public:
    virtual double area() const {
        return 0.0;
    }
};

class Circle : public Shape {
private:
    double radius;
public:
    Circle(double r) : radius(r) {}
    double area() const override {
        return 3.14159 * radius * radius;
    }
};

class Rectangle : public Shape {
private:
    double width;
    double height;
public:
    Rectangle(double w, double h) : width(w), height(h) {}
    double area() const override {
        return width * height;
    }
};

在上述代码中,Shape 类中的 area 函数是虚函数,CircleRectangle 类重写了 area 函数。

- **运行时多态**:通过基类指针或引用调用虚函数时,会根据指针或引用实际指向的对象类型来决定调用哪个类的虚函数实现,这就是运行时多态。例如:
int main() {
    Shape* shape1 = new Circle(5.0);
    Shape* shape2 = new Rectangle(4.0, 6.0);
    std::cout << "Circle area: " << shape1->area() << std::endl;
    std::cout << "Rectangle area: " << shape2->area() << std::endl;
    delete shape1;
    delete shape2;
    return 0;
}

在上述代码中,shape1shape2Shape 类的指针,分别指向 CircleRectangle 对象。通过这两个指针调用 area 函数时,会根据实际指向的对象类型调用相应类的 area 函数实现,从而实现运行时多态。

类的其他特性

友元函数与友元类

  1. 友元函数:友元函数是一种特殊的函数,它不是类的成员函数,但可以访问类的私有和保护成员。友元函数的声明需要在类体内部使用 friend 关键字。例如:
class Point {
private:
    int x;
    int y;
public:
    Point(int a, int b) : x(a), y(b) {}
    // 友元函数声明
    friend double distance(Point p1, Point p2);
};

double distance(Point p1, Point p2) {
    int dx = p1.x - p2.x;
    int dy = p1.y - p2.y;
    return std::sqrt(dx * dx + dy * dy);
}

在上述代码中,distance 函数是 Point 类的友元函数,它可以访问 Point 类的私有成员 xy

  1. 友元类:友元类的所有成员函数都是另一个类的友元函数,这意味着友元类的成员函数可以访问另一个类的私有和保护成员。例如:
class Rectangle;

class Point {
private:
    int x;
    int y;
public:
    Point(int a, int b) : x(a), y(b) {}
    // 友元类声明
    friend class Rectangle;
};

class Rectangle {
private:
    Point topLeft;
    Point bottomRight;
public:
    Rectangle(int x1, int y1, int x2, int y2) : topLeft(x1, y1), bottomRight(x2, y2) {}
    int getWidth() {
        return bottomRight.x - topLeft.x;
    }
    int getHeight() {
        return bottomRight.y - topLeft.y;
    }
};

在上述代码中,Rectangle 类是 Point 类的友元类,因此 Rectangle 类的成员函数可以访问 Point 类的私有成员 xy

嵌套类

嵌套类是在另一个类的内部定义的类。嵌套类可以访问外层类的所有成员,包括私有成员。例如:

class Outer {
private:
    int outerData;
public:
    Outer(int data) : outerData(data) {}
    // 嵌套类定义
    class Inner {
    private:
        int innerData;
    public:
        Inner(int data) : innerData(data) {}
        void printOuterData(const Outer& outer) {
            std::cout << "Outer data: " << outer.outerData << std::endl;
        }
    };
};

在上述代码中,Inner 类是 Outer 类的嵌套类。Inner 类的成员函数 printOuterData 可以访问 Outer 类的私有成员 outerData

静态成员

  1. 静态成员变量:静态成员变量是类的所有对象共享的变量,它不属于任何一个具体的对象。静态成员变量在类的定义中声明,在类外进行初始化。例如:
class Counter {
private:
    static int count;
public:
    Counter() {
        count++;
    }
    ~Counter() {
        count--;
    }
    static int getCount() {
        return count;
    }
};

// 静态成员变量初始化
int Counter::count = 0;

在上述代码中,countCounter 类的静态成员变量,通过 Counter 类的构造函数和析构函数对其进行增减操作。静态成员函数 getCount 用于获取当前的计数值。

  1. 静态成员函数:静态成员函数只能访问静态成员变量和其他静态成员函数,不能访问非静态成员变量和非静态成员函数,因为非静态成员属于具体的对象,而静态成员函数不依赖于任何具体对象。例如,上述 Counter 类中的 getCount 函数就是静态成员函数。

通过对 C++ 类与实例化的深入理解,开发者能够更好地利用面向对象编程的特性,编写出结构清晰、可维护性强且高效的代码。无论是简单的类定义与对象创建,还是复杂的继承、多态以及其他高级特性的应用,都为构建大型软件系统提供了坚实的基础。