类与对象(1)
1 类的引入
C++ 在 C 语言结构体的基础上引入了 类(class) 概念,用于实现面向对象编程。类将 数据(成员变量) 和 操作数据的方法(成员函数) 封装成一个整体,并支持访问控制、继承和多态等特性。
2 类的定义
2.1 核心结构
class ClassName {access_specifier: // 访问限定符member_variables; // 成员变量member_functions(); // 成员函数// 可包含多个访问限定区块
};
- 通过
class
或struct
关键字定义类(默认访问权限不同):
class ClassName { // class 默认 private 访问// 成员变量和成员函数
};struct StructName { // struct 默认 public 访问// 成员变量和成员函数
};
示例:
class Person {
public: // 公有成员void setName(std::string name) { _name = name; }void printInfo() { std::cout << "Name: " << _name << ", Age: " << _age << std::endl;}
private: // 私有成员std::string _name;int _age = 0; // C++11 支持成员变量初始化
};
2.2 关键要素
组成部分 | 说明 | 示例 |
---|---|---|
类关键字 | class (默认private) 或 struct (默认public) | class Student |
成员变量 | 描述对象状态的变量 | std::string name; |
成员函数 | 操作成员变量的函数 | void printInfo() { ... } |
访问限定符 | 控制访问权限的标签 | public: , private: |
2.3 成员函数定义
// 声明和定义分离
class Rectangle {
public:double area(); // 声明
private:double width, height;
};// 类外定义成员函数
double Rectangle::area() { return width * height;
}// 声明和定义合并
class Circle {
public:double area() { return 3.14 * radius * radius; }
private:double radius;
};
3 类的封装
限定符 | 类内访问 | 类外访问 | 继承访问 | 典型用途 |
---|---|---|---|---|
public | ✅ | ✅ | 保持原权限 | 对外接口 |
protected | ✅ | ❌ | 子类可访问 | 继承体系共享成员 |
private | ✅ | ❌ | 子类不可访问 | 隐藏实现细节 |
封装的核心思想:将数据和操作数据的方法绑定,并隐藏内部实现细节(通过 private
/protected)
仅暴露必要接口(public)
。
3.1 封装实践:
class BankAccount {
public:// 公有接口void deposit(double amount) { if(amount > 0) balance += amount; }double getBalance() const { return balance; }private:// 隐藏实现细节double balance = 0; std::string accountId;
};// 使用
BankAccount acc;
acc.deposit(100); // ✅ 允许
// acc.balance = 1000; // ❌ 编译错误:private成员
3.2 类的作用域
类定义了一个独立的作用域:
- 成员变量/函数在类内直接通过名字访问。
- 类外需使用作用域解析符
::
访问静态成员或定义成员函数。
void Person::setName(std::string name) { // 类外定义成员函数_name = name;
}
3.3 类的实例化
通过类创建对象的过程:
Person p1; // 栈上实例化
Person* p2 = new Person; // 堆上实例化
- 实例化时系统为对象分配内存。
- 对象生命周期:
- 栈对象:作用域结束时自动销毁。
- 堆对象:需手动
delete
释放。
4 类的大小及存储
4.1 大小
类的大小由成员变量决定(遵循内存对齐规则),与成员函数无关:
- 空类大小为 1 字节(占位标识)。
- 内存对齐原则:
- 成员偏移量为自身大小的整数倍。
- 第一个成员在结构体偏移量为0的地址处。
- 对齐数==编译器默认对齐数与该成员大小的较小值。
- 总大小为最大对齐数的整数倍。
- 虚函数会增加虚表指针(通常 4/8 字节)。
class A {char c; // 1 字节 → 首元素,对齐到结构体偏移量0处int i; // 4 字节→ ,对齐到偏移量为4处
}; // 大小 = 8 字节(char:1 + 3 填充为4字节)class B {int i; // 4 字节double d; // 8 字节,对齐数默认为8 → 偏移量需是 8 的倍数,故前面补 4 字节,到这占用了16个字节char c; // 1 字节 →虽然对齐数是1,在16后对齐1个字节,但 总大小需是 8 的倍数,末尾补 7 字节
}; // 大小 = 24 字节(4 + 4 :在double d前int i后填充4字节→16字节;在char c后填充 7字节,16+1+7 = 24)
4.2 存储
内存分布:
成员类型 | 存储位置 | 说明 |
---|---|---|
非静态成员变量 | 对象内存空间 | 每个对象独立副本 |
静态成员变量 | 全局数据区 | 所有对象共享 |
成员函数 | 代码段 | 所有对象共享同一份代码 |
虚函数 | 虚表(vtable) | 每个类一个虚表,对象含vptr |
5 类成员函数的 this 指针
5.1 核心机制
- 每个 非静态成员函数 隐含一个
this
指针参数(编译器自动添加)。 this
指向调用该函数的对象。- 通过
this
访问对象的成员变量/函数。
5.2 关键特性
- 隐式传递:函数调用
obj.func(x)
被转换为func(&obj, x)
。 - 类型约束:
this
是ClassName* const
类型(常量指针)。 - 显式使用:在成员函数中可直接使用
this
:
void Person::setName(std::string name) {this->_name = name; // 明确指明成员变量
}
特性 | 说明 |
---|---|
隐式存在 | 编译器自动添加到每个非静态成员函数的参数列表首部 |
类型固定 | ClassName* const (顶层const指针) |
指向当前对象 | 始终指向调用该成员函数的对象 |
不可修改性 | 指针值不可修改 (this = nullptr 非法) |
访问控制桥梁 | 通过this指针访问对象的所有成员(包括private) |
静态函数无this | 静态成员函数不能使用this指针 |
5.3 链式调用
Person& Person::setAge(int age) {_age = age;return *this; // 返回当前对象的引用
}
p.setAge(20).setName("Alice"); // 链式调用
5.4 空指针访问实验
class Test {
public:void safeFunc() { std::cout << "Safe function\n"; }void unsafeFunc() { std::cout << value; // 访问成员变量}static void staticFunc() {std::cout << "Static function\n";}
private:int value;
};Test* ptr = nullptr;
ptr->safeFunc(); // ✅ 正常运行:不访问成员变量
ptr->staticFunc(); // ✅ 正常运行:静态函数无this指针
// ptr->unsafeFunc(); // ❌ 崩溃:访问成员需要this指针
关键结论:
- 空指针可调用不访问成员变量的函数(包括静态函数)
- 访问成员变量时需通过有效的
this
指针,空指针导致崩溃
注意事项
- 静态成员函数无
this
指针(不能访问非静态成员)。 this
指针作为形参存在栈上or寄存器上。this
指针在成员函数开始执行前构造,结束后销毁。
6 总结
概念 | 关键点 |
---|---|
类定义 |
|
访问限定符 | public 、protected 、private 控制封装性 |
类作用域 | 类外定义成员函数需用 ClassName:: |
实例化 | 栈对象自动管理,堆对象需手动 new /delete |
类大小 | 由成员变量决定(内存对齐),不含成员函数和静态成员 |
this 指针 | 隐含指向当前对象的常量指针,实现成员访问和链式调用 |