C++基础:关于类与对象的学习记录

在C++的编程世界中,类和对象是面向对象编程(OOP)的核心基石。传统的面向过程编程侧重于"怎么做",而面向对象编程则侧重于"谁来做",它将数据和操作数据的方法绑定在一起,使得代码更具模块化、可重用性和易维护性。以下是我在职坐标的学习记录:

1.类和对象的概念:从现实到代码的映射

面向对象编程的核心思想是将现实世界中的事物抽象为代码中的"类"。你可以把"类"理解为一张设计蓝图,它定义了事物应该具备哪些属性(数据)和行为(方法)。而"对象"则是根据这张蓝图在内存中实际建造出来的具体实例。例如,"汽车"是一个类,而"你车库里的那辆红色特斯拉"就是一个对象。

#include <iostream>
using namespace std;

// 定义一个汽车类(蓝图)
class Car {
public:
    string brand; // 属性:品牌
    void start() { // 行为:启动
        cout << brand << " 汽车已启动!" << endl;
    }
};

int main() {
    Car myCar; // 创建对象(实例)
    myCar.brand = "Tesla";
    myCar.start(); // 调用对象的方法
    return 0;
}

代码讲解:上述代码定义了 Car 类,包含一个字符串属性 brand 和一个无参成员函数 start。在 main 函数中,我们通过 Car myCar; 创建了一个具体的对象,并为其属性赋值、调用其方法,完美展示了类与对象的协作关系。

2.类的定义:封装数据与行为

在C++中,使用 class 关键字来定义类。类内部可以包含成员变量和成员函数。为了保障数据安全,C++提供了访问控制修饰符:public(公开)、private(私有)和 protected(受保护)。

class Student {
public: // 公开区域:外部可以直接访问
    string name;
    void introduce() {
        cout << "我是" << name << ",今年" << age << "岁。" << endl;
    }

private: // 私有区域:外部无法直接访问,保证数据安全
    int age; 
};

代码讲解Student 类中,name 是公开的,外部可以随意读写;而 age 是私有的,外部无法直接修改,必须通过类内部提供的公开方法(如 introduce)来间接使用。

三大访问修饰符对比

访问修饰符 类内部访问 类外部访问 派生类访问 核心作用
public 允许 允许 允许 提供对外接口
private 允许 禁止 禁止 隐藏内部实现细节
protected 允许 禁止 允许 为继承体系预留接口

3.对象的创建与使用:栈与堆的区别

C++中创建对象有两种主要方式:在栈上直接创建,或在堆上通过指针动态创建。栈上对象生命周期由作用域决定,堆上对象需要手动管理内存。

int main() {
    // 1. 栈上创建(推荐,自动释放内存)
    Student s1; 
    s1.name = "张三";
    
    // 2. 堆上创建(需手动delete)
    Student* s2 = new Student();
    s2->name = "李四"; // 指针使用 -> 访问成员
    
    delete s2; // 必须手动释放内存,防止内存泄漏
    s2 = nullptr;
    return 0;
}

代码讲解:栈上的 s1 在离开 main 函数时会自动销毁;堆上的 s2 必须使用 -> 运算符访问成员,且用完必须 delete,否则会导致内存泄漏。

4.封装: Getter与Setter的最佳实践

封装是OOP的三大特性之一,其核心是将数据隐藏,并提供安全的访问接口。对于私有成员变量,我们通常提供 getter(获取)和 setter(设置)方法。

class BankAccount {
private:
    double balance;
public:
    // Setter:带数据校验的设置方法
    void setBalance(double amount) {
        if (amount >= 0) {
            balance = amount;
        } else {
            cout << "错误:余额不能为负数!" << endl;
        }
    }
    // Getter:安全的获取方法
    double getBalance() const { // const表示不修改成员变量
        return balance;
    }
};

代码讲解:通过 setBalance,我们拦截了非法数据(负数),保证了 balance 始终处于合法状态。这就是封装的魅力所在。

5.构造函数与析构函数:对象的生命周期管理

构造函数在对象创建时自动调用,用于初始化成员;析构函数在对象销毁时自动调用,用于清理资源。

class FileHandler {
private:
    string fileName;
public:
    // 普通构造函数
    FileHandler(string name) {
        fileName = name;
        cout << fileName << " 文件已打开。" << endl;
    }
    // 重载构造函数
    FileHandler() {
        fileName = "default.txt";
        cout << "默认文件已打开。" << endl;
    }
    // 析构函数
    ~FileHandler() {
        cout << fileName << " 文件已安全关闭。" << endl;
    }
};

代码讲解:当 FileHandler 对象被创建时,会根据传入的参数调用对应的构造函数;当对象超出作用域或被 delete 时,析构函数 ~FileHandler() 会自动执行,模拟关闭文件的操作。

6.构造函数的初始化列表:高效初始化的秘诀

对于 const 成员或引用成员,必须在初始化列表中赋值。同时,初始化列表比在构造函数体内赋值效率更高,因为它直接初始化,避免了先默认构造再赋值的开销。

class Config {
private:
    const int maxRetries;
    string path;
public:
    // 使用初始化列表
    Config(int retries, string p) : maxRetries(retries), path(p) {
        // 构造函数体可以为空
    }
};

代码讲解maxRetriesconst 常量,无法在函数体内被赋值,必须通过 : maxRetries(retries) 这种初始化列表的形式在对象诞生时直接赋予初值。

7.this指针:区分同名变量与链式调用

每个成员函数内部都有一个隐藏的 this 指针,它指向调用该函数的当前对象。常用于解决局部变量与成员变量同名的问题,或实现链式调用。

class Builder {
private:
    int width, height;
public:
    Builder& setWidth(int width) {
        this->width = width; // this->width 是成员变量,width 是局部参数
        return *this; // 返回当前对象的引用,支持链式调用
    }
    Builder& setHeight(int height) {
        this->height = height;
        return *this;
    }
};
// 使用:obj.setWidth(100).setHeight(200);

代码讲解:当参数名与成员变量名相同时,this->width 明确指向成员变量。返回 *this 使得我们可以像搭积木一样连续调用多个设置方法,代码更加优雅。

8.拷贝构造函数与深浅拷贝:内存管理的深水区

当用一个对象去初始化另一个新对象时,会调用拷贝构造函数。如果类中包含指针成员,默认的浅拷贝会导致两个对象指向同一块堆内存,引发"双重释放"崩溃。

class DeepArray {
private:
    int* data;
    int size;
public:
    DeepArray(int s) : size(s) { data = new int[size]; }
    
    // 深拷贝构造函数
    DeepArray(const DeepArray& other) {
        size = other.size;
        data = new int[size]; // 重新分配内存
        for(int i = 0; i < size; ++i) data[i] = other.data[i];
    }
    
    ~DeepArray() { delete[] data; } // 安全释放各自的内存
};

代码讲解:深拷贝在拷贝构造函数中为 data 重新 new 了一块内存,并逐个复制元素。这样两个对象拥有各自独立的内存空间,析构时互不干扰。

浅拷贝与深拷贝对比

对比维度 浅拷贝 (Shallow Copy) 深拷贝 (Deep Copy)
内存分配 仅复制指针的值(地址) 重新分配新的堆内存
数据独立性 两个对象共享同一块内存 两个对象拥有独立内存
析构风险 极易引发双重释放(Double Free) 安全释放,互不影响
适用场景 无指针成员或仅使用智能指针 包含裸指针且需手动管理内存

9.类与结构体struct的区别:不仅仅是默认权限

在C++中,structclass 在功能上几乎完全相同,都可以包含成员变量、成员函数、构造函数等。它们唯一的硬性区别在于默认的访问权限。

类与结构体对比

特性 class struct
默认访问权限 private public
默认继承权限 private public
语义习惯 强调封装、行为、面向对象 强调纯数据聚合、轻量级结构

10.类的声明与实现分离:工程化代码组织

在实际项目中,我们通常将类的声明放在 .h 头文件中,将成员函数的实现放在 .cpp 源文件中,以提高编译效率和代码可读性。

// === Calculator.h ===
#ifndef CALCULATOR_H
#define CALCULATOR_H

class Calculator {
private:
    int result;
public:
    Calculator(); // 声明构造函数
    void add(int num); // 声明成员函数
    int getResult() const;
};
#endif

// === Calculator.cpp ===
#include "Calculator.h"
#include <iostream>
using namespace std;

// 注意:类外实现必须加上 类名:: 作用域解析符
Calculator::Calculator() : result(0) {} 

void Calculator::add(int num) {
    result += num;
}

int Calculator::getResult() const {
    return result;
}

代码讲解:头文件使用 #ifndef 防止重复包含。在 .cpp 文件中实现函数时,必须使用 Calculator::add 这样的语法告诉编译器这个函数属于哪个类。这是C++大型项目开发的标准规范。

11.总结

类和对象是C++面向对象编程的灵魂。从基础的类定义、访问控制,到内存管理层面的构造/析构函数、深浅拷贝,再到工程化的声明与实现分离,掌握这些知识是进阶C++高手的必经之路。建议初学者多动手敲代码,在调试内存泄漏和编译错误中加深理解。

posted @ 2026-08-22 18:23  Solitude0420  阅读(8)  评论(0)    收藏  举报