C++面向对象与类和对象(一)----C++重要基础入门知识

xml 复制代码
hello,这里是AuroraWanderll。
兴趣方向:C++,算法,Linux系统,游戏客户端开发
欢迎关注,我将更新更多相关内容!

个人主页

这是类和对象系列的第一篇文章:

之前由于第一次发布时篇幅过长,可能导致阅读体验很差,现在我把他按要点进行了适当拆分,希望能帮助读者更好理解,也方便自己复习。

C++面向对象与类和对象(一)----C++重要基础入门知识

简易目录

  • 第一个重要问题:面向对象和面向过程的区别
  • 第二个重要问题:C++中结构体升级成了类
  • 第三个重要问题:类的定义与编码规范

第一个重要问题:面向对象和面向过程的区别

类和对象的知识是我们学习C++编程的基础,它在我们的知识体系中扮演了一个至关重要的角色。

但在开始学习类、对象、继承这些具体概念之前,我们首先要解决一个最根本的问题:到底什么是"面向对象"?它和我们可能更熟悉的"面向过程"编程有什么不同?

一、面向过程编程:关注步骤的"流水线"

核心思想: "怎么做?"------关注的是解决问题需要的一系列步骤或函数。

在面向过程的世界里,程序员像一个流水线工程师,他的任务是精确地设计出每一个操作步骤。对于"把大象装进冰箱"这个问题,他的思路会是这样的:

  1. 打开冰箱门。
  2. 把大象塞进去。
  3. 关上冰箱门。

面向过程的特点:

  • 核心是函数: 程序由一个个函数组成,数据(如 door(门的开关状态), content(冰箱中的内容))和操作数据的函数是分离的。
  • 线性思维: 代码按照预定的步骤顺序执行。
  • 优点: 流程直观,在解决小型、任务明确的问题时非常高效。

缺点: 当系统变得庞大复杂时,数据和函数的关系会变得混乱。如果想增加一个"给冰箱通电"的功能,相关的数据和函数可能会散落在程序的各个角落,难以维护和扩展。

二、面向对象编程:关注对象之间的相互作用

核心思想: "谁来做?"------关注的是解决问题中涉及到的各类对象,以及它们之间的交互。

在面向对象的世界里,程序员像一个社会架构师。他首先会定义出这个问题的解决方案里有哪几种角色( ),每种角色有什么属性(数据成员 )和能力(成员函数)。对于同样的问题,他的思路会是:

  1. 这个问题里涉及几个对象?------ 冰箱大象
  2. 每个对象各自有什么属性和能力?
    • 冰箱:有门(状态)、内部容量、可以执行开门、装东西、关门等动作。
    • 大象:有名字、体积等属性。

面向对象的特点:

  • 核心是类和对象: 程序是由多个对象组成的,对象是数据(属性)和操作(方法)的封装体。
  • 三大特性:
    • 封装: 把数据和处理数据的方法捆绑在一起,并可以隐藏内部细节(利用public,protected,private等访问限定符,后面会讲)。比如,main 函数不需要知道 Refrigerator(初始化冰箱状态) 内部如何记录门的状态,只需要调用它的 openDoor() 方法。
    • 继承: 允许我们基于已有的类创建新类,实现代码的复用和扩展。(后续章节详解)
    • 多态: 允许不同的对象对同一消息做出不同的响应。(后续章节详解)
  • 优点: 代码结构清晰,更接近现实世界,易于维护、扩展和复用。当需求变化时,通常只需要修改或扩展某个类,而不会影响整个程序,方便工程管理。
  1. 举个例子:我们在竞技游戏中,某个角色的技能出现了bug,例如王者荣耀的露娜大招不刷新的bug。我们会说策划怎么还不修复露娜大招bug。而这个说法说的正是对象!我们的露娜是一个对象。如果我们是面向过程,我们则会说哪一步出了问题,而不是谁谁谁出了问题。

    可以看出,面向对象似乎比面向过程更贴近我们人的思维方式

第二个重要问题:C++中结构体升级成了类

在C语言中,我们学习的struct已经具有了很强大的功能,它能够在内部定义变量,我们用它来实现了很多很多数据结构。

而现在,在C++中,我们不在拘泥于定义变量,我们的对象,需要有更多的功能与方法,来进行更加复杂却便利的交互。

一、从C的 struct 到C++的 class:一次伟大的升级

在C语言中,struct 只是一个数据集合 ,它允许你将不同的数据类型组合在一起,形成一个新的复合类型。但它不能包含函数(方法)。

我们的struct中现在不仅可以定义变量,我们还可以定义包含方法,而我们所说的这些struct的功能已经无限接近class的功能。

C语言的 struct(数据包):

c

复制代码
// C语言代码
struct Person_C {
    char name[20];
    int age;
}; 
// 只能定义数据,不能定义函数
// 操作这个结构体的函数必须与结构体分离
void printPerson(struct Person_C p) {
    printf("Name: %s, Age: %d\n", p.name, p.age);
}

到了C++,class 的概念被引入,它极大地扩展了 struct 的能力。C++中的 class 是一个数据与行为的封装体

C++的 class(智能对象):

复制代码
// C++代码
class Person_CPP {
private:
    std::string name; 
    int age;

public:
    // 可以在类内部定义函数(方法)
    void setName(const std::string& newName) {
        name = newName;
    }
    void setAge(int newAge) {
        if(newAge > 0) { // 可以加入逻辑验证
            age = newAge;
        }
    }
    void print() const { // 成员函数
        std::cout << "Name: " << name << ", Age: " << age << std::endl;
    }
};

升级的核心点:

  1. 数据封装:可以包含成员变量(数据)和成员函数(行为)。
  2. 访问控制 :引入了 publicprivateprotected 关键字来控制成员的访问权限。
  3. 构造函数/析构函数:提供了对象自动初始化和清理的能力。
  4. 继承与多态:成为面向对象继承体系的基础。

可以说我们的C++中的struct已经无限接近于class,但是他们还有一些细微的区别。

二、C++中的 structclass:孪生兄弟的细微差别

现在来到问题的关键:C++中的 structclass 有什么区别?

答案是:除了默认的成员访问权限和默认的继承方式,它们没有任何功能上的区别。 C++对原有的C风格 struct 进行了增强,让它拥有了 class 的所有能力。

核心区别:默认访问权限

  • class :成员和继承默认是 private 的。
  • struct :成员和继承默认是 public 的。

让我们用代码来展示这个区别:

示例1:默认成员访问权限

复制代码
// 使用 class 关键字
class MyClass {
    int data; // 默认为 private,外部无法直接访问
public:
    void setData(int d) { data = d; }
};

// 使用 struct 关键字
struct MyStruct {
    int data; // 默认为 public,外部可以直接访问
    void setData(int d) { data = d; }
};

int main() {
    MyClass obj_c;
    // obj_c.data = 10; // 错误!data 是 private 成员
    obj_c.setData(10); // 必须通过公共接口

    MyStruct obj_s;
    obj_s.data = 20; // 正确!data 是 public 成员
    obj_s.setData(20); // 当然也可以这样做,也就是说通过公共接口或是直接访问都可以

    return 0;
}

示例2:默认继承方式

复制代码
class Base {
public:
    int x;
};

// class 默认是 private 继承
class DerivedClass : Base { // 等价于 : private Base
    // 在DerivedClass内部,Base的public成员x变成了private
};

// struct 默认是 public 继承
struct DerivedStruct : Base { // 等价于 : public Base
    // 在DerivedStruct内部,Base的public成员x仍然是public
};

int main() {
    DerivedClass d_c;
    // d_c.x = 10; // 错误!由于是private继承,x在派生类中不可见

    DerivedStruct d_s;
    d_s.x = 10; // 正确!由于是public继承,x在派生类中保持public

    return 0;
}

重要提示 :尽管默认行为不同,但我们强烈建议在代码中显式地写出访问控制符和继承方式,以避免混淆。

复制代码
// 好的实践:显式声明
class MyClass {
private:
    int data;
public:
    MyClass() = default;
};

struct MyStruct {
public: // 虽然默认就是public,但写上更清晰
    int data;
    MyStruct() = default;
};

class Derived : public Base { // 显式声明public继承
    // ...
};
三、如何选择:struct vs class

既然功能几乎一样,我们该如何选择?C++社区形成了一些约定俗成的惯例

  • 使用 struct
    • 当你主要需要一个纯粹的数据结构时。
    • 当所有成员都希望是公共的时(例如:坐标点 Point {x, y},配置参数 Config {width, height})。
    • 用于与C代码交互的兼容性结构体。
    • 在模板元编程中,有时用 struct 来作为"编译期函数"或特性标签,因为它默认的public更便捷。
  • 使用 class
    • 当你需要定义一个具有复杂行为严格封装的抽象数据类型(Abstract Data Type)时。
    • 当你需要用到私有成员、保护成员,并需要通过公共接口来与对象交互时。
    • 当你打算使用继承和多态等面向对象特性时(尽管 struct 也可以,但 class 的语义更贴切)。

简单来说:struct 感觉更像一个"数据包",而 class 感觉更像一个"功能完整的对象"。

总结
  • C++的 class 是C语言 struct 的超级增强版,引入了成员函数和访问控制。
  • 在C++中,structclass几乎完全相同的概念
  • 它们唯一的核心区别在于默认的访问权限和继承方式。
  • 在实际编程中,根据语义和惯例来选择使用哪一个,并始终显式地写出访问控制符,这能让你的代码更清晰、更专业。

理解了这一点,你就可以自信地在C++中使用这两种关键字了。

第三个重要问题:类的定义与编码规范

理解了面向对象的思想和classstruct的关系后,现在让我们正式学习如何在C++中定义类。这就像学习一门新语言的语法规则一样,是打好基础的关键一步。

一、类的基本语法结构
复制代码
class ClassName 
{
    // 类体:由成员函数和成员变量组成
};  // 一定要注意这个分号!
  • class:定义类的关键字
  • ClassName :类的名字(遵循大驼峰命名法,如MyClass
  • {}:类的主体,包含类的所有成员(成员变量啊,成员函数啊等等)
  • ; :类定义结束的分号绝对不能省略!这是很多初学者容易犯错的地方。

类的成员分为两类:

  • 成员变量(属性):描述对象的特征,如人的年龄、姓名

  • 成员函数(方法):描述对象的行为,如人会走路、说话

    比如我们定义一个学生类,那么他的成员变量就是学号,姓名,年龄

    他的成员函数就是写作业,考试,上课

    就好像语文中的名词与动词的区别一样。

二、类的两种定义方式
方式1:声明和定义全部放在类体中
复制代码
class Date {
public:
    // 成员函数直接在类内部定义
    void Init(int year, int month, int day) {
        _year = year;
        _month = month;
        _day = day;
    }
    
    void Print() {
        std::cout << _year << "-" << _month << "-" << _day << std::endl;
    }

private:
    int _year;
    int _month;
    int _day;
};

特点:

  • 优点:写法简单,适合教学和小型项目

  • 注意 :在类内部定义的成员函数,编译器可能会将其当作内联函数处理

    这里我们可以简单了解一下内联函数(不是重点,可以跳过):

    传统函数调用过程:

    1. 跳转到函数地址
    2. 保存现场(压栈)
    3. 执行函数体
    4. 恢复现场(出栈)
    5. 跳转回调用处

    内联函数的处理方式: 编译器会将内联函数的代码体直接展开到调用处,避免函数调用的开销(省略了压栈出栈一系列步骤)。

重要提醒 :即使是显式使用 inline 关键字,也只是给编译器的建议,不是命令!编译器最终还是会根据自己的判断来决定是否真正内联。

内联的优缺点

优点:

  • 消除函数调用开销,提高性能
  • 避免跳转指令,有利于CPU指令流水线

缺点:

  • 代码膨胀:每个调用处都展开一份函数体

  • 可能增加编译后文件大小

  • 调试困难(函数调用栈信息可能丢失)

    可能被当成内联函数这一点是需要我们避免的,在这里编译器有完全的自主权,到底有没有被当成内联函数我们不得而知。这是一种不确定性,而且内联可能导致编译完之后代码膨胀(因为展开)

方式2:声明和定义分离(推荐!)

Date.h(头文件):

复制代码
class Date {
public:
    // 只在类中声明函数
    void Init(int year, int month, int day);
    void Print();

private:
    int _year;
    int _month;
    int _day;
};

Date.cpp(源文件):

复制代码
#include "Date.h"
#include <iostream>

// 在源文件中定义函数,需要加上类名和作用域解析符 ::
void Date::Init(int year, int month, int day) {
    _year = year;
    _month = month;
    _day = day;
}

void Date::Print() {
    std::cout << _year << "-" << _month << "-" << _day << std::endl;
}

为什么推荐方式2?

  • 分离编译:头文件只放声明,源文件放实现,符合软件工程原则
  • 减少依赖:修改函数实现时,只需要重新编译对应的.cpp文件
  • 代码清晰:接口和实现分离,更易于阅读和维护
  • 避免内联膨胀:不会意外产生大量的内联函数(与上文相呼应)
三、成员变量命名规范(重要!)

这是一个看似简单但极其重要的实践问题。先看一个反面教材:

复制代码
class Date {
public:
    void Init(int year) {
        // 灾难!这里的year到底是成员变量还是函数参数?
        year = year; // 自己赋值给自己,毫无意义!
    }
    
private:
    int year; // 成员变量
};

上面的代码中,year = year 实际上只是把参数赋值给自己,成员变量完全没有被修改!

这样既导致了严重的南辕北辙的逻辑错误,也使阅读代码变成非常困难的一件事。比我们在C语言中的普通的变量命名规范重要的多,后者只是规范而前者是你必须好好命名否则会有严重后果

解决方案:使用命名区分

方案1:使用下划线前缀(常见于Linux/Unix风格)

复制代码
class Date {
public:
    void Init(int year) {
        _year = year; // 清晰明了!
    }
    
private:
    int _year;    // 成员变量加下划线
    int _month;
    int _day;
};

方案2:使用'm'前缀(常见于Windows/MFC风格)

复制代码
class Date {
public:
    void Init(int year) {
        mYear = year; // 同样清晰!
    }
    
private:
    int mYear;    // 成员变量加m前缀
    int mMonth;
    int mDay;
};
核心原则:
  • 一致性:选择一个风格并在整个项目中坚持使用
  • 可读性:让成员变量在代码中一眼就能被认出来
  • 避免歧义:确保不会与函数参数或局部变量混淆

在实际工作中,请遵循你所在团队的编码规范。没有绝对的对错,只有一致性最重要!

xml 复制代码
感谢你能够阅读到这里,如果本篇文章对你有帮助,欢迎点赞收藏支持,关注我,
我将更新更多有关C++,Linux系统·网络部分的知识。
相关推荐
草莓熊Lotso2 小时前
哈希表封装 myunordered_map/myunordered_set 实战:底层原理 + 完整实现
服务器·开发语言·数据结构·c++·人工智能·哈希算法·散列表
sali-tec10 小时前
C# 基于halcon的视觉工作流-章66 四目匹配
开发语言·人工智能·数码相机·算法·计算机视觉·c#
小明说Java10 小时前
常见排序算法的实现
数据结构·算法·排序算法
45288655上山打老虎10 小时前
C++完美转发
java·jvm·c++
行云流水201911 小时前
编程竞赛算法选择:理解时间复杂度提升解题效率
算法
SunkingYang12 小时前
程序崩溃闪退——MFC共享内存多次OpenFileMapping和MapViewOfFile而没有相应的UnmapViewOfFile和CloseHandle
c++·mfc·共享内存·崩溃·闪退·close·openfilemapping
问君能有几多愁~12 小时前
C++ 日志实现
java·前端·c++
smj2302_7968265212 小时前
解决leetcode第3768题.固定长度子数组中的最小逆序对数目
python·算法·leetcode
JANGHIGH13 小时前
c++ 多线程(二)
开发语言·c++