柏虎资源网

专注编程学习,Python、Java、C++ 教程、案例及资源

C++ 面向对象编程零基础入门_c++面向对象程序设计基础教程

1. 面向对象与面向过程的比较

想象一下,你要盖一栋房子

面向过程 (Procedure-Oriented):就像你自己亲力亲为地去盖。你的思维是线性的:我先去搬砖 -> 然后和水泥 -> 接着砌墙 -> 最后封顶。你关注的是步骤,是一步一步的动词(动作)。写代码就像写菜谱,第一步做什么,第二步做什么。

// 面向过程的C语言风格代码示例:做一顿饭
void washVegetables() { /* 洗菜 */ }
void cutVegetables() { /* 切菜 */ }
void cook() { /* 炒菜 */ }

int main() {
    washVegetables(); // 第一步:洗
    cutVegetables();  // 第二步:切
    cook();           // 第三步:炒
    return 0;
}

面向对象 (Object-Oriented):就像你是一个建筑师包工头。你的思维是先设计蓝图,再组织分工。你会先想:“我需要一栋房子,这栋房子由门、窗、墙、屋顶组成”。这里的“房子”、“门”、“窗”就是对象。你关注的是事物,是名词(东西),以及这些东西各自有什么功能(方法)。

// 面向对象的C++风格代码示例:组织一顿饭
class Chef { // 1. 先设计一个“厨师”蓝图
public:
    void washVegetables() { /* 厨师能洗菜 */ }
    void cutVegetables() { /* 厨师能切菜 */ }
    void cook() { /* 厨师能炒菜 */ }
};

int main() {
    Chef chefWang; // 2. 根据蓝图,请来一个叫chefWang的厨师(创建一个对象)
    // 3. 指挥厨师去做事(调用对象的方法)
    chefWang.washVegetables();
    chefWang.cutVegetables();
    chefWang.cook();
    return 0;
}

核心区别

  • 面向过程:关心步骤,自己干。
  • 面向对象:关心事物,指挥别人(对象)干。代码更模块化,更易维护和复用,就像乐高积木,每个零件(对象)各司其职。

为了更直观地理解两者的工作流程差异,请看下面的流程图:

2. 初步理解C++的类

“类”(Class)是面向对象的核心概念。刚才的“厨师蓝图”就是一个类。我们再打个比方:

  • 类 (Class):就像是汽车的设计图纸。图纸上规定了这辆车应该有:
  • 属性 (Attributes):比如颜色、品牌、最高时速。这些是名词,在类里叫成员变量
  • 行为 (Methods):比如加速、刹车、鸣笛。这些是动词,在类里叫成员函数
  • 对象 (Object):就是根据那张设计图纸制造出来的真实汽车。根据一张图纸,可以造出千千万万辆真实的汽车。

让我们用代码来画一张“小狗”的设计图:

#include <iostream>
#include <string>
using namespace std; // 为了代码简洁,先告诉编译器我们使用标准命名空间

// 1. 画一张“小狗”的设计图(定义一个类)
class Dog {
// 通常会把属性设为private,像锁在笼子里,保护起来,防止被随意修改。后面会细讲。
private:
    string name; // 小狗的名字(成员变量)
    int age;     // 小狗的年龄(成员变量)

// 通常把行为设为public,像公共接口,谁都可以调用。
public:
    // 2. 小狗的行为(成员函数)
    void setName(string dogName) { // 给小狗起名的方法
        name = dogName;
    }

    void setAge(int dogAge) { // 设置小狗年龄的方法
        age = dogAge;
    }

    void bark() { // 小狗叫的方法
        cout << name << " says: Wang Wang! I'm " << age << " years old." << endl;
        // cout 可以理解为“打印到屏幕上”, endl 是换行。
        // << 可以想象成“把内容输送过去”。
    }
};

int main() {
    // 3. 根据图纸制造一只真实的小狗(创建一个对象)
    Dog myDog;

    // 4. 指挥我的小狗做事(调用对象的方法)
    myDog.setName("Wang Cai"); // 给我的小狗起名叫“旺财”
    myDog.setAge(2);           // 设置它的年龄为2岁
    myDog.bark();              // 让我的小狗叫

    return 0;
}

输出结果:

Wang Cai says: Wang Wang! I'm 2 years old.

看,我们不需要关心bark()函数内部是怎么实现的,我们只需要知道“让小狗叫”这个行为就行了。这就是封装:把复杂的实现细节隐藏起来,只暴露简单的使用接口。

下面我们用一张类图来更形象地展示Dog类的设计:

图示:Dog类将数据(name, age)私有(-)封装,只对外提供公共(+)的方法来交互。

3. this指针的分析

现在有个问题:在类的成员函数内部,如果参数名字和成员变量名字一样怎么办?比如:

void setName(string name) {
    name = name; // 哪个name是参数?哪个name是成员变量?分不清了!
}

这就好比你在一个房间里,房间里有一个大喇叭(成员变量),你手里拿着一个小喇叭(参数)。你说“喇叭开喊”,别人怎么知道是哪个喇叭?

为了解决这个问题,C++提供了一个叫做 this指针 的秘密武器。

  • this指针:可以理解为对象自己的“身份证”或者“自指针”。在任何一个成员函数内部,this指针都默默地指向调用这个函数的那个对象自己

我们用this来修改上面的代码:

class Dog {
private:
    string name;
    int age;

public:
    void setName(string name) { // 参数名和成员变量名可以相同了
        this->name = name; // this->name 表示“我自己的name”,
                           // 等号右边的name是传进来的参数。
        // 这就好比说:“我自己的名字(this->name) = 你传过来的新名字(name)”
    }

    void setAge(int age) {
        this->age = age; // “我自己的年龄 = 你传过来的新年龄”
    }

    void bark() {
        // 即使这里不用,this指针也是存在的,可以写成:
        // cout << this->name << " says: Wang Wang!" << endl;
        cout << name << " says: Wang Wang!" << endl; // 通常name前隐含了this->
    }
};

一句话记住this:this就像每个对象心里的“我”字,它总是指向自己。当出现名字冲突时,用this->来明确指明“我自己的那个变量”。

4. 析构函数与构造函数

现在我们来学习两个特殊的函数:对象的“出生证明”和“遗嘱”。

构造函数 (Constructor)

  • 它是什么:一个特殊的、在创建对象时自动调用的成员函数。
  • 它的作用:初始化对象,为对象的成员变量赋初值。就像孩子一出生,医生就会给他登记出生日期、体重、身高。
  • 它的特点
  • 函数名与类名完全相同
  • 没有返回值类型(连void都没有)。

析构函数 (Destructor)

  • 它是什么:一个特殊的、在对象被销毁时自动调用的成员函数。
  • 它的作用:清理善后,比如释放对象在生命周期内申请的内存、关闭打开的文件等。就像一个人临终前要立下遗嘱,处理自己的财产。
  • 它的特点
  • 函数名是在类名前加一个**波浪号~**。
  • 没有返回值类型,也没有参数
#include <iostream>
using namespace std;

class Dog {
private:
    string name;
    int* ptr; // 一个指针,用来演示析构函数的作用

public:
    // 1. 构造函数(可以重载,有多个版本)
    Dog() { // 默认构造函数(无参)
        name = "Unnamed";
        ptr = new int(0); // 在对象创建时,动态申请一块内存
        cout << "A dog named \"" << name << "\" is born! (Default Constructor)" << endl;
    }

    Dog(string name, int age) { // 带参数的构造函数
        this->name = name;
        ptr = new int(age); // 申请内存,并用age的值初始化
        cout << "A dog named \"" << name << "\" is born! (Parameterized Constructor)" << endl;
    }

    // 2. 析构函数(只有一个,没有参数)
    ~Dog() {
        delete ptr; // 在对象销毁时,释放之前申请的内存,防止内存泄漏
        cout << "The dog named \"" << name << "\" says goodbye... (Destructor)" << endl;
    }

    void bark() {
        cout << name << " says: Wang Wang! I'm " << *ptr << " years old." << endl;
    }
};

int main() {
    cout << "Start of main" << endl;

    { // 一个新的作用域开始
        Dog dog1; // 调用默认构造函数,对象dog1诞生!
        Dog dog2("Buddy", 3); // 调用带参构造函数,对象dog2诞生!

        dog1.bark();
        dog2.bark();
    } // 作用域结束,dog1和dog2的生命周期到了,它们的析构函数会被自动调用!

    cout << "End of main" << endl;
    return 0;
}

输出结果:

Start of main
A dog named "Unnamed" is born! (Default Constructor)
A dog named "Buddy" is born! (Parameterized Constructor)
Unnamed says: Wang Wang! I'm 0 years old.
Buddy says: Wang Wang! I'm 3 years old.
The dog named "Buddy" says goodbye... (Destructor)
The dog named "Unnamed" says goodbye... (Destructor)
End of main

注意看析构函数调用的顺序:后创建的对象(dog2)先被销毁,先调用析构函数,就像拆房子要从上往下拆一样。

5. const成员

const是“常量”(constant)的缩写,意思是“不变的”。它有两种主要用法:

1. const对象(常量对象)

就像你买了一个“防修改保护套”给对象套上,声明这个对象自身是常量,不可修改。

const Dog myDog("Lucky", 5); // 创建一个常量小狗Lucky
// myDog.setName("Not Lucky"); // 错误!常量对象不能调用可能修改其内容的函数!
myDog.bark(); // 如果bark()函数保证不会修改对象,那么它就可以被调用

这就引出了下一个问题:如何让bark()函数向编译器保证它不会修改对象呢?

2. const成员函数(常量成员函数)

在成员函数的参数列表后面加上const关键字,这个函数就变成了常量成员函数。它向编译器和使用者承诺:“我绝不会修改这个对象里的任何成员变量”。

class Dog {
    // ... 其他成员同上 ...

    // 常量成员函数:承诺不会修改对象状态
    void bark() const { // 注意函数声明后的const
        // this->age = 10; // 错误!在const成员函数内不允许修改成员变量!
        cout << name << " says: Wang Wang! (I promise I won't change anything!)" << endl;
    }

    // 非常量成员函数:允许修改对象
    void haveBirthday() {
        (*ptr)++; // 年龄加1
    }
};

int main() {
    const Dog lucky("Lucky", 5); // 常量对象
    lucky.bark(); // 正确:bark()是const函数,可以被常量对象调用
    // lucky.haveBirthday(); // 错误!haveBirthday()不是const函数,常量对象不能调用

    Dog regularDog("Regular", 2); // 非常量对象
    regularDog.bark(); // 正确:非常量对象可以调用const函数
    regularDog.haveBirthday(); // 正确:非常量对象也可以调用非const函数
    return 0;
}

生活化比喻

  • const对象就像一个被玻璃罩罩住的博物馆展品,只能看,不能摸。
  • const成员函数就像是展品旁边一个“只读”的讲解员,他只会给你讲解(读数据),绝不会动手改变展品(写数据)。

6. static成员

static意思是“静态的”。类的static成员(变量或函数)有一个非常特别的属性:它不属于任何一个对象,而是属于整个类

  • 静态成员变量:所有对象共享的同一个变量。就像班级里的公共班费,班费不属于任何一个学生,但每个学生都可以知道和使用它。
  • 静态成员函数:只能操作静态成员变量的函数。就像班级的生活委员,他管理的只是班费(静态变量),而不是某个学生的私人零花钱(普通成员变量)。
#include <iostream>
using namespace std;

class Dog {
private:
    string name;
    // 静态成员变量:记录创建了多少只小狗(不属于任何一只狗,属于Dog类本身)
    static int count; // 仅仅是在类内声明,需要在类外单独定义和初始化

public:
    Dog(string n) : name(n) {
        count++; // 每创建一只小狗,计数器就+1
        cout << name << " joined! Total dogs: " << count << endl;
    }
    ~Dog() {
        count--; // 每消失一只小狗,计数器就-1
        cout << name << " left! Total dogs: " << count << endl;
    }

    // 静态成员函数:它没有this指针,因为它不属于某个特定对象
    static int getCount() {
        // cout << name; // 错误!静态函数不能访问普通的非静态成员变量(name)
        return count; // 正确!静态函数可以访问静态成员变量
    }
};

// 在类外定义和初始化静态成员变量(很重要!)
int Dog::count = 0; // “::”是作用域解析运算符,意思是“Dog类里的count”

int main() {
    cout << "Initial dog count: " << Dog::getCount() << endl; // 通过类名直接访问静态函数

    Dog* dog1 = new Dog("Wang Cai");
    cout << "Now, total dogs: " << Dog::getCount() << endl; // 输出:1

    {
        Dog dog2("Xiao Bai");
        cout << "Now, total dogs: " << Dog::getCount() << endl; // 输出:2
        // 也可以通过对象访问静态函数,但它属于类,不属于对象
        cout << "Same count, accessed by dog2: " << dog2.getCount() << endl; // 输出:2
    } // dog2析构,count减1

    cout << "After Xiao Bai left: " << Dog::getCount() << endl; // 输出:1
    delete dog1;

    cout << "Final dog count: " << Dog::getCount() << endl; // 输出:0
    return 0;
}

**记住static**:它是类的“公共财产”和“公共服务”,与具体的对象无关。

7. 友元

在C++中,类的privateprotected成员是受保护的,外部函数和其他类的函数不能访问。这就像你的私家卧室,外人不能进。

但有时候,你可能会希望你的最好的朋友可以破例进入你的卧室。friend(友元)就是这个“破例”。

  • 友元函数:一个非成员函数(普通函数)被授予了访问某个类的私有成员的权力。
  • 友元类:一个类的所有成员函数都被授予了访问另一个类私有成员的权力。

注意:友元关系是单向的,也不具有传递性(你朋友的朋友不是你的朋友)。要谨慎使用友元,因为它破坏了封装性。

#include <iostream>
using namespace std;

// 提前声明Dog类,这样Friend函数才知道它的存在
class Dog;

// 一个全局函数(非成员函数)
void friendFunction(const Dog& d);

class Dog {
private:
    string secret; // 小狗的秘密,私有成员

public:
    Dog() : secret("I hid my bone under the tree!") {} // 初始化秘密

    // 声明友元函数!注意,这是在类的内部声明的。
    // 这句声明就是在说:“friendFunction函数是我的朋友,让它进我的私人房间!”
    friend void friendFunction(const Dog& d);
};

// 实现友元函数
void friendFunction(const Dog& d) {
    // 因为这个函数是Dog类的友元,所以它可以访问Dog的私有成员secret
    cout << "The dog tells me a secret: \"" << d.secret << "\"" << endl;
    // 如果不是友元,这行代码会报错:无法访问private成员
}

int main() {
    Dog myDog;
    // myDog.secret; // 错误!main函数不是友元,不能访问私有成员。
    friendFunction(myDog); // 正确!友元函数可以访问。
    return 0;
}

输出结果:

The dog tells me a secret: "I hid my bone under the tree!"

友元比喻:就像公司里,所有员工的薪资都是保密的(private),但HR经理的职能需要查看所有人的薪资,那么就可以把HR经理设为所有员工的friend。但普通员工之间不能互相查看薪资。


总结

好啦,今天我们用了大量的比喻和简单的代码,学习了C++面向对象最最基础的概念。我们来回顾一下:

概念 中文 核心比喻 Class & Object 类与对象 设计图纸 vs 真实产品 this pointer this指针 对象的身份证 / “我” Constructor/Destructor 构造函数/析构函数 出生证明 / 遗嘱 const Member const成员 玻璃罩展品 / 只读讲解员 static Member static成员 公共班费 / 生活委员 friend 友元 最好的朋友(特权)

面向对象编程的思想就是:将数据和操作数据的方法捆绑在一起,形成一个“对象”,然后通过对象之间的交互来完成复杂的程序。它更符合我们人类对现实世界的认知方式。

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言