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、最佳实践建议
-
对于简单类,可以在类体中直接定义成员函数
-
对于复杂类,推荐使用声明和定义分离的方式
-
保持成员变量命名一致性
-
优先使用
class
而非struct
来定义类 -
注意资源管理(如示例中的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++通过访问限定符实现面向对象编程中的封装特性,这是将数据与操作数据的方法有机结合的重要机制。访问限定符允许开发者精确控制类成员的可见性,从而:
-
保护内部数据不被随意修改
-
提供清晰的使用接口
-
隐藏实现细节,降低耦合度
C++提供了三种访问限定符来控制类成员的可见性:
-
public(公有成员):公有成员可以在类外直接被访问,构成类的对外接口。
-
protected(受保护成员):受保护成员只能在类内部和派生类中访问,类外不可直接访问。示例:派生类中可访问基类protected成员
-
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有何区别?
标准答案:
-
兼容性区别:struct保持了对C语言结构体的兼容,可以像C结构体一样使用。C++完全兼容C的struct用法、C++的struct可以包含成员函数
-
默认访问权限:struct成员默认是public的,class成员默认是private的
-
继承默认权限:struct继承默认是public继承,class继承默认是private继承
-
模板参数: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、示例说明
基本示例: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、类域的重要性
-
名称查找规则:类域影响编译器的查找规则。当在类外定义成员函数时:
-
如果不指定类域,编译器会把函数当作全局函数处理
-
指定类域后,编译器知道这是成员函数,会在类域中查找其他成员
-
-
避免命名冲突:类作用域可以避免成员名称与全局名称的冲突
-
组织代码:类作用域帮助组织代码,使成员函数的实现与声明分离,提高代码可读性
4、常见错误
如果在类外定义成员函数时忘记指定类域,会导致编译错误:
// 错误写法:缺少Stack::
void Init(int n) { /*...*/ } // 编译器会认为这是全局函数
编译器会报错,因为在这个"全局函数"中访问的array
、capacity
等成员无法找到定义。