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

发布于:2025-08-06 ⋅ 阅读:(14) ⋅ 点赞:(0)

目录

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

一、类的定义

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 { // 默认为public
        int 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等成员无法找到定义。 


网站公告

今日签到

点亮在社区的每一天
去签到