当前位置: 首页 > news >正文

C++面向对象编程基础:从类定义到封装机制详解

目录

前言:面向过程与面向对象的初步认识

一、类的定义

1、类的引入

2、类的定义

3、成员变量命名规范

4、类的两种定义方式

声明和定义全部放在类体中

特点:

声明和定义分离

头文件(person.h):

源文件(person.cpp):

特点:

5、类使用示例(先了解,后面详讲)

6、最佳实践建议

二、类成员变量:声明与定义的边界剖析——从私有成员到内存分配(先了解,等学到了后面再回顾)

1、声明 vs 定义的本质区别

2、类成员变量的特殊情况

3、需要特别注意的情况

4、为什么这样设计?

关键结论

三、C++类的访问控制与封装机制

1、封装的概念与实现

2、访问限定符

作用与意义

重要区别

访问控制规则

最佳实践建议:

3、struct与class的区别

4、封装的本质与意义

四、类的作用域详解

1、类作用域的基本概念

2、示例说明

基本示例:Person类

深入理解:Stack类

3、类域的重要性

4、常见错误


前言:面向过程与面向对象的初步认识

  • C语言是面向过程的编程语言,关注的是过程,通过分析问题的解决步骤,用函数调用来逐步解决问题。
  • C++是基于面向对象的编程语言,关注的是对象,将问题分解为不同的对象,通过对象之间的交互来完成功能。

一、类的定义

1、类的引入

在C++中,结构体(struct)不仅可以定义变量,还可以定义函数:

struct Test
{// 成员变量int a;double b;// 成员函数int Add(int x, int y){return x + y;}
};

但在C++中更常用class关键字来定义类。

2、类的定义

class className
{// 类体:由成员变量和成员函数组成};  // 注意后面的分号
  • class是定义类的关键字

  • className是类名

  • 类体包含成员变量(属性)和成员函数(方法)

  • 类定义结束时必须加上分号

3、成员变量命名规范

为了区分成员变量和局部变量,通常会对成员变量添加特殊标识:

class Date 
{
private:int _year;   // 前面加下划线int m_month; // m开头int day_;    // 后面加下划线
};

注意:这只是编程惯例,并非C++强制要求,具体命名规范可能因公司而异。

4、类的两种定义方式

声明和定义全部放在类体中

class Person  
{  
public:  // 显示基本信息  void ShowInfo()  {  cout << _name << "-" << _sex << "-" << _age << endl;  }  
private:  char* _name; // 姓名  char* _sex;  // 性别  int _age;    // 年龄  
};
特点
  • 代码紧凑,适合简单类

  • 在类中定义的成员函数默认可能被当作内联函数处理

声明和定义分离

头文件(person.h):
class Person  
{  
public:  // 显示基本信息  void ShowInfo();  
private:  char* _name;  // 姓名  char* _sex;   // 性别  int _age;     // 年龄  
};
源文件(person.cpp):
#include "person.h"  
#include <iostream>  // 显示基本信息  
void Person::ShowInfo()  
{  std::cout << _name << "__" << _sex << "__" << _age << std::endl;  
}
特点
  • 提高代码可读性和可维护性

  • 减少头文件的依赖

  • 避免潜在的重复定义问题

  • 更符合大型项目的组织规范

5、类使用示例(先了解,后面详讲)

int main() 
{// Stack类使用示例Stack st;st.Init();st.Push(1);st.Push(2);cout << st.Top() << endl;st.Destroy();// Date类使用示例Date d;d.Init(2024, 3, 31);return 0;
}

6、最佳实践建议

  1. 对于简单类,可以在类体中直接定义成员函数

  2. 对于复杂类,推荐使用声明和定义分离的方式

  3. 保持成员变量命名一致性

  4. 优先使用class而非struct来定义类

  5. 注意资源管理(如示例中的malloc/free)


二、类成员变量:声明与定义的边界剖析——从私有成员到内存分配(先了解,等学到了后面再回顾)

1、声明 vs 定义的本质区别

  • 声明(Declaration):向编译器介绍标识符(变量/函数)的存在和类型信息,但不分配内存

    int x;  // 声明(也是定义,特殊情况)
    extern int y; // 纯声明
  • 定义(Definition):完成声明的同时分配存储空间

    int x = 10; // 定义

2、类成员变量的特殊情况

在类定义中的成员变量(无论private/public/protected都是声明

class Example {
private: int a;    // 声明(未分配内存)double b; // 声明
};

内存分配时机:只有当创建类的具体对象时,这些成员变量才会被真正定义(分配内存)

Example obj; // 此时obj.a和obj.b才被定义(分配内存)

3、需要特别注意的情况

  • 静态成员变量:类内声明,类外必须单独定义(因为需要独立存储)

    class Example {
    private:static int count; // 声明
    };
    int Example::count = 0; // 必须类外定义
  • 成员变量的初始化:C++11后支持类内直接初始化(仍是声明)

    class Example {
    private:int a = 10; // 声明带默认值(C++11特性)
    };

4、为什么这样设计?

  • 抽象性:类定义只是"蓝图",不占用实际内存

  • 灵活性:允许不同编译单元包含相同的类定义

  • 效率:避免多次定义导致的存储冲突

关键结论

  • 类定义中的成员变量(包括私有成员)都是声明
  • 实际内存分配发生在实例化对象
  • 静态成员变量需要额外在类外定义
  • 类内初始化(C++11)是语法糖,不改变声明本质

三、C++类的访问控制与封装机制

1、封装的概念与实现

        C++通过类(class)机制实现面向对象编程中的封装特性。封装是将对象的属性(数据成员)和方法(成员函数)有机结合在一起,通过访问权限控制,有选择性地对外提供接口,同时隐藏内部实现细节。

2、访问限定符

作用与意义

        C++通过访问限定符实现面向对象编程中的封装特性,这是将数据与操作数据的方法有机结合的重要机制。访问限定符允许开发者精确控制类成员的可见性,从而:

  1. 保护内部数据不被随意修改

  2. 提供清晰的使用接口

  3. 隐藏实现细节,降低耦合度

C++提供了三种访问限定符来控制类成员的可见性:

  1. public(公有成员):公有成员可以在类外直接被访问,构成类的对外接口。

  2. protected(受保护成员):受保护成员只能在类内部和派生类中访问,类外不可直接访问。示例:派生类中可访问基类protected成员

  3. private(私有成员):私有成员只能在类内部访问,类外部和派生类都不可直接访问(默认访问级别)

重要区别

        protected和private在当前类中表现相同,区别仅体现在继承关系中(派生类可访问基类protected成员,但不能访问private成员)

访问控制规则

  • 访问权限从访问限定符出现的位置开始生效,直到遇到下一个访问限定符或类定义结束(即遇到}

  • class的默认访问权限是private,struct的默认访问权限是public(为了兼容C语言的结构体)

重要说明

        访问限定符仅在编译阶段起作用,当程序运行时,所有成员在内存中的布局没有区别,访问控制纯粹是编译器级别的保护机制。

最佳实践建议:

成员变量:通常设为private/protected

  • 防止外部直接修改

  • 可通过公有成员函数控制访问

  • 示例:

    class Person {
    private:std::string name; // 私有成员变量
    public:void setName(const std::string& n) { name = n; }std::string getName() const { return name; }
    };

成员函数

  • 对外接口设为public

  • 内部辅助函数设为private

  • 示例:

    class Calculator {
    public:double add(double a, double b) { return a + b; }
    private:void logOperation(const std::string& op) { /* 记录操作日志 */ }
    };

struct的特殊用法

  • 当需要POD(Plain Old Data)类型时使用struct

  • 需要完全公开成员时使用struct

  • 示例:

    struct Point { // 默认为publicint x; int y;
    };

3、struct与class的区别

        在C++中,struct也可以用于定义类。虽然C++保留了C语言中struct的用法,但将其功能进行了扩展,使其升级为完整的类。最显著的变化是struct现在可以包含成员函数。不过在实际开发中,我们通常更推荐使用class来定义类。

面试常见问题:C++中struct和class有何区别?

标准答案

  1. 兼容性区别:struct保持了对C语言结构体的兼容,可以像C结构体一样使用。C++完全兼容C的struct用法C++的struct可以包含成员函数

  2. 默认访问权限:struct成员默认是public的,class成员默认是private的

  3. 继承默认权限:struct继承默认是public继承,class继承默认是private继承

  4. 模板参数:class可作为模板参数关键字,struct不能

        除此之外,struct和class在功能上完全等价,都可以用于定义类,包含成员函数、实现继承等面向对象特性。

// C风格结构体
typedef struct ListNodeC 
{struct ListNodeC* next;int val;
} LTNode;// C++风格结构体
struct ListNodeCPP 
{void Init(int x) {next = nullptr;val = x;}ListNodeCPP* next;int val;
};

4、封装的本质与意义

封装本质上是一种管理机制,其核心思想是:

  1. 数据保护:隐藏对象的内部状态和实现细节

  2. 接口暴露:仅对外提供必要的访问和操作接口

  3. 使用约束:通过接口限制对数据的随意修改

在软件开发中,封装带来的好处包括:降低耦合度、提高安全性、便于修改和维护、简化接口使用

        良好的封装设计是高质量面向对象程序的基础,它能有效隔离变化,提高代码的稳定性和可维护性。


四、类的作用域详解

1、类作用域的基本概念

        类定义了一个新的作用域,类的所有成员(包括数据成员和成员函数)都在这个类的作用域中。当在类体外定义成员时,需要使用::作用域解析符来指明成员属于哪个类域。

2、示例说明

基本示例:Person类

class Person {
public:// 声明成员函数void ShowInfo();
private:char* _name;  // 姓名char* _sex;   // 性别int _age;     // 年龄
};// 在类外定义成员函数时需要指定类域
void Person::ShowInfo() {cout << _name << "-" << _sex << "-" << _age << endl;
}

深入理解:Stack类

#include <iostream>
using namespace std;class Stack {
public:// 成员函数声明void Init(int n = 4);
private:// 成员变量int* array;size_t capacity;size_t top;
};// 成员函数定义,必须指定类域
void Stack::Init(int n) {array = (int*)malloc(sizeof(int) * n);if (nullptr == array) {perror("malloc申请空间失败");return;}capacity = n;top = 0;
}int main() {Stack st;st.Init();  // 使用默认参数初始化return 0;
}

3、类域的重要性

  1. 名称查找规则:类域影响编译器的查找规则。当在类外定义成员函数时:

    • 如果不指定类域,编译器会把函数当作全局函数处理

    • 指定类域后,编译器知道这是成员函数,会在类域中查找其他成员

  2. 避免命名冲突:类作用域可以避免成员名称与全局名称的冲突

  3. 组织代码:类作用域帮助组织代码,使成员函数的实现与声明分离,提高代码可读性

4、常见错误

如果在类外定义成员函数时忘记指定类域,会导致编译错误:

// 错误写法:缺少Stack::
void Init(int n) { /*...*/ }  // 编译器会认为这是全局函数

编译器会报错,因为在这个"全局函数"中访问的arraycapacity等成员无法找到定义。 

http://www.dtcms.com/a/316437.html

相关文章:

  • 【Linux网络编程】socket基础
  • 风丘助力混合动力汽车工况测试:精准采集整车信号解决方案
  • Datawhale AI夏令营 第三期 task2 稍微改进
  • P1026 [NOIP 2001 提高组] 统计单词个数
  • 计算机网络:详解路由器如何转发子网数据包
  • Java JDBC连接池深度解析与实战指南
  • SAP PP CK466
  • 解决docker load加载tar镜像报json no such file or directory的错误
  • jQuery中Ajax返回字符串处理技巧
  • Window.structuredClone() 指南
  • 基于深度学习钢铁表面缺陷检测系统(yolov8/yolov5)
  • 《算法导论》第 3 章 - 函数的增长
  • 本地配置运行https协议
  • Spring依赖注入:从原理到实践的自学指南
  • Linux 调度器函数sched_*系统调用及示例
  • 【数据结构入门】单链表和数组的OJ题(1)
  • 基于ARM+FPGA光栅数据采集卡设计
  • OpenCV学习 day5
  • 从「同步」到「异步」:用 aiohttp 把 Python 网络 I/O 榨到极致
  • Python--OCR(2)
  • 微算法科技(NASDAQ:MLGO)基于量子重加密技术构建区块链数据共享解决方案
  • 算法438. 找到字符串中所有字母异位词
  • 算法第31天|动态规划:最后一块石头的重量Ⅱ、目标和、一和零
  • 二分查找
  • 算法训练营day41 动态规划⑧ 121. 122.123.买卖股票的最佳时机1.2.3
  • 常用技术资料链接
  • Spring小细节
  • oelove奥壹新版v11.7旗舰版婚恋系统微信原生小程序源码上架容易遇到的几个坑,避免遗漏参数白屏显示等问题
  • Electron-updater + Electron-builder + IIS + NSIS + Blockmap 完整增量更新方案
  • 物联网后端系统架构:从基础到AI驱动的未来 - 第十章:AI促进IOT领域发生革命式发展