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

C++类的基本概念与创建方式

2023-05-246.7k 阅读

C++ 类的基本概念

类的定义

在 C++ 中,类是一种用户自定义的数据类型,它封装了数据成员(变量)和成员函数(方法)。类就像是一个模板,用于创建对象,这些对象具有相同的属性和行为。例如,我们想要描述“学生”这个概念,学生有姓名、年龄、成绩等属性,同时可能有学习、考试等行为。我们可以定义一个 Student 类来描述它。

定义类的一般语法如下:

class ClassName {
    // 访问修饰符
    private:
        // 私有数据成员和成员函数
        data_type data_member;
        return_type member_function(parameters);
    public:
        // 公有数据成员和成员函数
        data_type data_member;
        return_type member_function(parameters);
    protected:
        // 保护数据成员和成员函数
        data_type data_member;
        return_type member_function(parameters);
};

其中,ClassName 是类的名称,大括号内是类的主体,包含数据成员和成员函数的声明。privatepublicprotected 是访问修饰符,用于控制对类成员的访问权限。

访问修饰符

  1. private(私有):私有成员只能在类的内部被访问,类的外部无法直接访问。这提供了数据的隐藏和封装,确保数据的安全性。例如:
class Rectangle {
private:
    int length;
    int width;
public:
    void setDimensions(int l, int w) {
        length = l;
        width = w;
    }
    int calculateArea() {
        return length * width;
    }
};

在上面的 Rectangle 类中,lengthwidth 是私有数据成员,只能通过类中的公有成员函数 setDimensionscalculateArea 来访问和操作。

  1. public(公有):公有成员可以在类的内部和外部被访问。通常将需要被外部调用的成员函数声明为 public。例如,在 Rectangle 类中的 setDimensionscalculateArea 函数就是公有成员函数,外部代码可以通过对象来调用它们。
int main() {
    Rectangle rect;
    rect.setDimensions(5, 3);
    int area = rect.calculateArea();
    return 0;
}
  1. protected(保护):保护成员与私有成员类似,只能在类的内部被访问。不同的是,保护成员可以被派生类(子类)访问。这在继承的场景中非常有用。例如:
class Shape {
protected:
    int color;
public:
    void setColor(int c) {
        color = c;
    }
};

class Circle : public Shape {
public:
    void printColor() {
        std::cout << "Circle color: " << color << std::endl;
    }
};

在上述代码中,Shape 类的 color 成员是保护的,Circle 类作为 Shape 类的派生类,可以访问 color 成员。

类的成员函数

成员函数是定义在类内部的函数,它可以访问类的所有成员(包括私有成员)。成员函数可以在类的内部定义,也可以在类的外部定义。

  1. 在类内部定义成员函数
class Counter {
private:
    int count;
public:
    void increment() {
        count++;
    }
    int getCount() {
        return count;
    }
};
  1. 在类外部定义成员函数:当成员函数的定义比较复杂时,为了提高代码的可读性,通常在类的外部定义。在类外部定义成员函数时,需要使用作用域解析运算符 ::
class Counter {
private:
    int count;
public:
    void increment();
    int getCount();
};

void Counter::increment() {
    count++;
}

int Counter::getCount() {
    return count;
}

类的数据成员

数据成员是类中定义的变量,用于存储对象的状态。数据成员可以是基本数据类型(如 intfloat 等),也可以是自定义数据类型(如其他类的对象)。

  1. 基本数据类型数据成员
class Point {
private:
    int x;
    int y;
public:
    void setCoordinates(int a, int b) {
        x = a;
        y = b;
    }
    void printCoordinates() {
        std::cout << "(" << x << ", " << y << ")" << std::endl;
    }
};
  1. 自定义数据类型数据成员
class Date {
private:
    int day;
    int month;
    int year;
public:
    void setDate(int d, int m, int y) {
        day = d;
        month = m;
        year = y;
    }
    void printDate() {
        std::cout << day << "/" << month << "/" << year << std::endl;
    }
};

class Employee {
private:
    std::string name;
    Date hireDate;
public:
    void setEmployeeDetails(std::string n, int d, int m, int y) {
        name = n;
        hireDate.setDate(d, m, y);
    }
    void printEmployeeDetails() {
        std::cout << "Name: " << name << std::endl;
        std::cout << "Hire Date: ";
        hireDate.printDate();
    }
};

Employee 类中,hireDateDate 类的对象,作为 Employee 类的数据成员。

C++ 类的创建方式

创建对象

类是一种抽象的概念,而对象是类的具体实例。创建对象的过程称为实例化。有几种常见的创建对象的方式:

  1. 栈上创建对象
class MyClass {
private:
    int value;
public:
    MyClass(int v) : value(v) {}
    void printValue() {
        std::cout << "Value: " << value << std::endl;
    }
};

int main() {
    MyClass obj(10); // 在栈上创建对象
    obj.printValue();
    return 0;
}

在上述代码中,MyClass obj(10); 在栈上创建了一个 MyClass 类的对象 obj,并通过构造函数初始化 value 为 10。

  1. 堆上创建对象:使用 new 关键字在堆上分配内存来创建对象,返回的是对象的指针。
class MyClass {
private:
    int value;
public:
    MyClass(int v) : value(v) {}
    void printValue() {
        std::cout << "Value: " << value << std::endl;
    }
    ~MyClass() {
        std::cout << "Object destroyed" << std::endl;
    }
};

int main() {
    MyClass* ptr = new MyClass(20); // 在堆上创建对象
    ptr->printValue();
    delete ptr; // 释放堆上的内存
    return 0;
}

这里使用 new MyClass(20) 在堆上创建了一个 MyClass 对象,并将其地址赋给指针 ptr。使用完对象后,需要使用 delete 关键字释放内存,以避免内存泄漏。

  1. 动态数组对象:可以创建类对象的动态数组。
class MyClass {
private:
    int value;
public:
    MyClass(int v) : value(v) {}
    void printValue() {
        std::cout << "Value: " << value << std::endl;
    }
};

int main() {
    MyClass* arr = new MyClass[3]{MyClass(1), MyClass(2), MyClass(3)};
    for (int i = 0; i < 3; i++) {
        arr[i].printValue();
    }
    delete[] arr;
    return 0;
}

上述代码在堆上创建了一个包含 3 个 MyClass 对象的动态数组,并通过循环调用每个对象的 printValue 函数。注意,释放动态数组时要使用 delete[]

构造函数

构造函数是一种特殊的成员函数,用于在创建对象时初始化对象的数据成员。构造函数的名称与类名相同,并且没有返回类型。

  1. 默认构造函数:如果类中没有显式定义构造函数,编译器会自动生成一个默认构造函数。默认构造函数不接受参数,并且不对数据成员进行任何初始化(对于基本数据类型,其值是未定义的)。
class MyClass {
private:
    int value;
public:
    // 默认构造函数由编译器自动生成
    void printValue() {
        std::cout << "Value: " << value << std::endl;
    }
};

int main() {
    MyClass obj;
    obj.printValue(); // value 的值是未定义的
    return 0;
}
  1. 带参数的构造函数:通常我们会定义带参数的构造函数来对数据成员进行初始化。
class MyClass {
private:
    int value;
public:
    MyClass(int v) : value(v) {}
    void printValue() {
        std::cout << "Value: " << value << std::endl;
    }
};

int main() {
    MyClass obj(10);
    obj.printValue();
    return 0;
}

在上述代码中,MyClass(int v) : value(v) {} 是带参数的构造函数,使用初始化列表 : value(v)value 初始化为传入的参数 v

  1. 构造函数的重载:一个类可以有多个构造函数,只要它们的参数列表不同,这就是构造函数的重载。
class MyClass {
private:
    int value1;
    int value2;
public:
    MyClass() : value1(0), value2(0) {}
    MyClass(int v1) : value1(v1), value2(0) {}
    MyClass(int v1, int v2) : value1(v1), value2(v2) {}
    void printValues() {
        std::cout << "Value1: " << value1 << ", Value2: " << value2 << std::endl;
    }
};

int main() {
    MyClass obj1;
    MyClass obj2(10);
    MyClass obj3(10, 20);
    obj1.printValues();
    obj2.printValues();
    obj3.printValues();
    return 0;
}

这里定义了三个构造函数,分别对应不同的初始化需求。

析构函数

析构函数与构造函数相反,用于在对象被销毁时释放对象占用的资源。析构函数的名称是在类名前加上波浪线 ~,没有参数和返回类型。

  1. 默认析构函数:如果类中没有显式定义析构函数,编译器会自动生成一个默认析构函数。默认析构函数通常用于释放对象的数据成员所占用的资源(如动态分配的内存)。
class MyClass {
private:
    int* data;
public:
    MyClass(int size) {
        data = new int[size];
    }
    // 默认析构函数由编译器自动生成
};

int main() {
    MyClass obj(5);
    // 当 obj 离开作用域时,默认析构函数会被调用,释放 data 所指向的内存
    return 0;
}
  1. 自定义析构函数:当需要手动管理资源(如文件句柄、网络连接等)时,需要定义自定义析构函数。
class MyClass {
private:
    int* data;
public:
    MyClass(int size) {
        data = new int[size];
    }
    ~MyClass() {
        delete[] data;
    }
};

int main() {
    MyClass obj(5);
    // 当 obj 离开作用域时,自定义析构函数会被调用,释放 data 所指向的内存
    return 0;
}

在上述代码中,自定义析构函数 ~MyClass() 使用 delete[] 释放了在构造函数中动态分配的数组 data

拷贝构造函数

拷贝构造函数用于创建一个新对象,该对象是另一个已存在对象的副本。拷贝构造函数的参数是本类对象的引用。

  1. 默认拷贝构造函数:如果类中没有显式定义拷贝构造函数,编译器会自动生成一个默认拷贝构造函数。默认拷贝构造函数执行浅拷贝,即简单地将源对象的数据成员的值复制到目标对象。
class MyClass {
private:
    int value;
public:
    MyClass(int v) : value(v) {}
    // 默认拷贝构造函数由编译器自动生成
    void printValue() {
        std::cout << "Value: " << value << std::endl;
    }
};

int main() {
    MyClass obj1(10);
    MyClass obj2(obj1); // 使用默认拷贝构造函数
    obj2.printValue();
    return 0;
}
  1. 自定义拷贝构造函数:当类中包含动态分配的资源时,默认拷贝构造函数的浅拷贝可能会导致问题(如内存泄漏),此时需要定义自定义拷贝构造函数进行深拷贝。
class MyClass {
private:
    int* data;
public:
    MyClass(int size) {
        data = new int[size];
        for (int i = 0; i < size; i++) {
            data[i] = i;
        }
    }
    MyClass(const MyClass& other) {
        int size = sizeof(other.data) / sizeof(int);
        data = new int[size];
        for (int i = 0; i < size; i++) {
            data[i] = other.data[i];
        }
    }
    ~MyClass() {
        delete[] data;
    }
    void printData() {
        for (int i = 0; i < sizeof(data) / sizeof(int); i++) {
            std::cout << data[i] << " ";
        }
        std::cout << std::endl;
    }
};

int main() {
    MyClass obj1(5);
    MyClass obj2(obj1);
    obj1.printData();
    obj2.printData();
    return 0;
}

在上述代码中,自定义拷贝构造函数 MyClass(const MyClass& other) 为新对象 obj2 分配了独立的内存,并将 obj1 的数据复制到 obj2,避免了浅拷贝带来的问题。

赋值运算符重载

赋值运算符 = 用于将一个对象的值赋给另一个对象。与拷贝构造函数类似,编译器会自动生成一个默认的赋值运算符重载函数,执行浅拷贝。当类中有动态分配的资源时,需要自定义赋值运算符重载函数进行深拷贝。

class MyClass {
private:
    int* data;
public:
    MyClass(int size) {
        data = new int[size];
        for (int i = 0; i < size; i++) {
            data[i] = i;
        }
    }
    MyClass(const MyClass& other) {
        int size = sizeof(other.data) / sizeof(int);
        data = new int[size];
        for (int i = 0; i < size; i++) {
            data[i] = other.data[i];
        }
    }
    MyClass& operator=(const MyClass& other) {
        if (this == &other) {
            return *this;
        }
        delete[] data;
        int size = sizeof(other.data) / sizeof(int);
        data = new int[size];
        for (int i = 0; i < size; i++) {
            data[i] = other.data[i];
        }
        return *this;
    }
    ~MyClass() {
        delete[] data;
    }
    void printData() {
        for (int i = 0; i < sizeof(data) / sizeof(int); i++) {
            std::cout << data[i] << " ";
        }
        std::cout << std::endl;
    }
};

int main() {
    MyClass obj1(5);
    MyClass obj2(3);
    obj2 = obj1;
    obj1.printData();
    obj2.printData();
    return 0;
}

在上述代码中,MyClass& operator=(const MyClass& other) 是自定义的赋值运算符重载函数。首先检查是否是自赋值,如果是则直接返回。然后释放目标对象已有的资源,重新分配内存并复制数据,最后返回目标对象的引用。

通过深入理解 C++ 类的基本概念与创建方式,开发者能够更好地利用面向对象编程的特性,编写出更健壮、可维护和高效的代码。无论是封装数据、控制访问权限,还是通过构造函数、析构函数等对对象进行初始化和资源管理,都是 C++ 编程中非常重要的环节。同时,对于拷贝构造函数和赋值运算符重载的正确处理,能够避免潜在的内存泄漏和数据错误等问题。在实际项目开发中,根据具体需求灵活运用这些知识,能够提高代码的质量和开发效率。例如,在大型软件系统中,合理设计类的结构和成员,通过对象的创建和管理来组织和处理复杂的数据和逻辑,是实现高效、可靠软件的关键。在开发游戏引擎、操作系统组件、数据库管理系统等领域,C++ 类的良好运用都起着举足轻重的作用。同时,随着 C++ 标准的不断演进,类的特性也在不断丰富和完善,开发者需要持续学习和掌握新的知识,以适应不断变化的技术需求。比如,C++11 引入了移动语义和右值引用,这对于处理临时对象和提高性能有着重要意义,在涉及类对象的传递和赋值场景中可以更好地优化资源利用。总之,扎实掌握 C++ 类的相关知识,是成为优秀 C++ 开发者的必经之路。在日常编程中,要不断实践和总结经验,通过实际项目来加深对这些概念的理解和运用能力。同时,参考优秀的开源代码库,学习他人在类设计和对象管理方面的最佳实践,也是提升编程水平的有效途径。例如,在一些知名的图形库如 OpenGL 相关的 C++ 封装库中,就有很多精妙的类设计和对象创建、管理的方式值得学习。此外,在多线程编程环境下,类的设计和对象的使用还需要考虑线程安全问题,这也进一步拓展了对类相关知识的应用范围。例如,通过使用互斥锁等机制来保护类的共享数据成员,确保在多线程环境下对象的正确访问和操作。在分布式系统开发中,类和对象的概念同样重要,需要考虑对象在不同节点之间的传输和状态同步等问题,这也促使开发者从更宏观的角度去理解和运用类的知识。总之,C++ 类的基本概念与创建方式是 C++ 编程的核心内容,对其深入理解和熟练掌握将为开发者打开更广阔的编程天地。