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

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.xdnf.cn/news/17211.html

相关文章:

  • 深度学习-卷积神经网络CNN-填充与步幅
  • 最新基于Python科研数据可视化实践技术
  • 【人工智能99问】什么是Post-Training,包含哪些内容?(19/99)
  • Next Terminal 实战:内网无密码安全登录
  • MCP进阶:工业协议与AI智能体的融合革命
  • Redis之Hash和List类型常用命令
  • VGMP(VRRP Group Management Protocol)VRRP组管理协议
  • Druid学习笔记 02、快速使用Druid的SqlParser解析
  • Solidity全局变量与安全实践指南
  • python中的字典
  • 雷达系统工程学习:自制极化合成孔径雷达无人机
  • bypass
  • SelectDB:新一代实时数仓的核心引擎与应用实战
  • 机器学习——基本算法
  • 笛卡尔坐标
  • Java 中 BigDecimal、Float、Double 的取整与保留小数处理方法详解
  • 简要探讨大型语言模型(LLMs)的发展历史
  • Android进程基础:Zygote
  • Linux 磁盘管理与分区配置
  • 【2025WACV-最佳论文】RayGauss:基于体积高斯的光线投射,用于逼真的小说视图合成
  • (JAVA)自建应用调用企业微信API接口,设置企业可信IP
  • 前端开发(HTML,CSS,VUE,JS)从入门到精通!第五天(jQuery函数库)
  • 使用1panel将http升级至https的过程
  • 板子指示灯状态设计
  • ESDocValues机制
  • Easysearch 集成阿里云与 Ollama Embedding API,构建端到端的语义搜索系统
  • python与C++
  • web第一次作业
  • Spring Cloud Gateway 实现登录校验:构建统一认证入口
  • Kali基础知识点【2】