1. C++类与对象核心概念解析
C++作为一门面向对象的编程语言,类和对象是其最核心的特性。理解类和对象的关系,就像理解建筑图纸和实际建筑的关系——图纸定义了结构,而建筑是具体的实体。
1.1 类的基本定义与实例化
类的定义语法看似简单,但包含许多关键细节:
cpp复制class Stack {
public:
// 成员函数
void Init(int n = 4) {
array = (int*)malloc(sizeof(int) * n);
if(nullptr == array) {
perror("malloc申请空间失败");
return;
}
capacity = n;
top = 0;
}
private:
// 成员变量
int* array;
size_t capacity;
size_t top;
};
这里有几个关键点需要注意:
- 类定义结束时必须加分号,这是新手常犯的错误
- 成员变量通常加前缀(如
_或m)以区分局部变量 - 类内定义的成员函数默认是内联的(inline)
实例化对象时,编译器会为每个对象分配独立的内存空间。例如:
cpp复制Stack s1; // 实例化第一个栈对象
Stack s2; // 实例化第二个栈对象
1.2 访问控制与封装
C++通过访问限定符实现封装:
| 访问限定符 | 类内访问 | 类外访问 | 继承访问 |
|---|---|---|---|
| public | ✔ | ✔ | ✔ |
| protected | ✔ | ✖ | ✔ |
| private | ✔ | ✖ | ✖ |
一个良好的设计实践是:
- 成员变量设为private
- 对外接口设为public
- protected用于继承体系
cpp复制class Date {
public:
void Print() { /* 可以访问私有成员 */ }
private:
int _year; // 封装起来,防止外部直接修改
};
2. 类的六大默认成员函数详解
2.1 构造函数与初始化列表
构造函数是对象创建的入口点,有几种常见形式:
cpp复制class Date {
public:
// 1. 无参构造
Date() : _year(1970), _month(1), _day(1) {}
// 2. 带参构造
Date(int y, int m, int d) : _year(y), _month(m), _day(d) {}
// 3. 全缺省构造(与无参构造冲突)
Date(int y=1970, int m=1, int d=1) : _year(y), _month(m), _day(d) {}
};
初始化列表的使用场景:
- 初始化const成员
- 初始化引用成员
- 初始化没有默认构造的类成员
注意:成员变量的初始化顺序取决于声明顺序,而非初始化列表顺序
2.2 拷贝控制:三/五法则
对于管理资源的类,通常需要自定义以下三个函数:
- 析构函数 - 释放资源
- 拷贝构造函数 - 深拷贝资源
- 赋值运算符 - 安全赋值
cpp复制class String {
public:
// 构造函数
String(const char* str = "") {
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
// 拷贝构造
String(const String& s) : _size(s._size), _capacity(s._capacity) {
_str = new char[_capacity + 1];
strcpy(_str, s._str);
}
// 赋值运算符
String& operator=(String s) {
swap(s); // 拷贝交换技术
return *this;
}
// 析构函数
~String() {
delete[] _str;
_str = nullptr;
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
2.3 移动语义(C++11)
现代C++增加了移动构造和移动赋值:
cpp复制class String {
public:
// 移动构造
String(String&& s) noexcept
: _str(s._str), _size(s._size), _capacity(s._capacity) {
s._str = nullptr; // 置空源对象
}
// 移动赋值
String& operator=(String&& s) noexcept {
if(this != &s) {
delete[] _str;
_str = s._str;
_size = s._size;
_capacity = s._capacity;
s._str = nullptr;
}
return *this;
}
};
3. 类的高级特性
3.1 static成员
静态成员属于类而非对象:
cpp复制class Counter {
public:
Counter() { ++count; }
~Counter() { --count; }
static int getCount() { return count; }
private:
static int count; // 声明
};
int Counter::count = 0; // 定义初始化
静态成员的特点:
- 类内声明,类外初始化
- 没有this指针
- 可以被所有对象共享
3.2 友元与内部类
友元打破了封装,应谨慎使用:
cpp复制class Matrix;
class Vector {
friend Vector operator*(const Matrix& m, const Vector& v);
private:
float data[4];
};
class Matrix {
friend Vector operator*(const Matrix& m, const Vector& v);
private:
float data[4][4];
};
Vector operator*(const Matrix& m, const Vector& v) {
Vector result;
// 可以直接访问两个类的私有成员
for(int i=0; i<4; ++i) {
result.data[i] = 0;
for(int j=0; j<4; ++j) {
result.data[i] += m.data[i][j] * v.data[j];
}
}
return result;
}
内部类是一个独立的类,但受外部类作用域限制:
cpp复制class Outer {
public:
class Inner {
public:
void accessOuter(Outer& o) {
cout << o._data; // 可以访问外部类私有成员
}
};
private:
int _data = 42;
};
4. 实战:C++与C实现栈的对比
4.1 C语言实现方式
c复制typedef struct {
int* data;
int top;
int capacity;
} Stack;
void StackInit(Stack* ps, int cap) {
ps->data = (int*)malloc(sizeof(int)*cap);
ps->top = 0;
ps->capacity = cap;
}
void StackPush(Stack* ps, int val) {
if(ps->top == ps->capacity) {
// 扩容逻辑
}
ps->data[ps->top++] = val;
}
C实现的缺点:
- 数据和操作分离
- 没有访问控制
- 需要显式传递this指针
4.2 C++面向对象实现
cpp复制class Stack {
public:
explicit Stack(int cap = 4)
: _data(new int[cap]), _top(0), _capacity(cap) {}
~Stack() { delete[] _data; }
void push(int val) {
if(_top == _capacity) {
resize(_capacity * 2);
}
_data[_top++] = val;
}
private:
void resize(int new_cap) {
int* new_data = new int[new_cap];
memcpy(new_data, _data, sizeof(int)*_top);
delete[] _data;
_data = new_data;
_capacity = new_cap;
}
int* _data;
int _top;
int _capacity;
};
C++实现的优势:
- 数据和方法封装在一起
- 自动管理资源(RAII)
- 更自然的调用语法
5. 常见问题与解决方案
5.1 对象模型相关问题
问题1:空类的大小是多少?
- 答案是1字节,用于占位标识
问题2:成员函数存储在哪里?
- 成员函数存储在代码段,不在对象内部
问题3:this指针的本质是什么?
- this是成员函数的隐式参数,类型是
ClassName* const
5.2 构造函数常见错误
错误1:忘记初始化const成员
cpp复制class A {
public:
A(int v) { _val = v; } // 错误!const成员必须在初始化列表初始化
private:
const int _val;
};
错误2:初始化列表顺序错误
cpp复制class B {
public:
B(int x) : b(a), a(x) {} // 危险!实际初始化顺序是a先于b
private:
int a;
int& b;
};
5.3 资源管理建议
- 遵循RAII原则:资源获取即初始化
- 使用智能指针管理动态内存
- 对于不可复制的资源,禁用拷贝构造和赋值
cpp复制class FileHandle {
public:
explicit FileHandle(const char* filename)
: handle(fopen(filename, "r")) {
if(!handle) throw std::runtime_error("文件打开失败");
}
~FileHandle() { if(handle) fclose(handle); }
// 禁用拷贝
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
// 允许移动
FileHandle(FileHandle&& other) noexcept
: handle(other.handle) {
other.handle = nullptr;
}
private:
FILE* handle;
};
6. 性能优化与最佳实践
6.1 内联函数的选择
类内定义的成员函数默认内联,但要注意:
- 小型函数适合内联
- 递归函数不应内联
- 虚函数通常不能内联
cpp复制class Point {
public:
// 隐式内联
int x() const { return _x; }
// 显式内联
inline void setX(int x) { _x = x; }
private:
int _x, _y;
};
6.2 返回值优化(RVO)
现代编译器可以优化返回值拷贝:
cpp复制Vector createVector() {
Vector v; // 直接在返回位置构造
// ... 操作v
return v; // 可能触发RVO
}
6.3 异常安全保证
提供不同级别的异常安全保证:
- 基本保证 - 不泄露资源
- 强保证 - 操作要么完成要么回滚
- 不抛保证 - 承诺不抛出异常
cpp复制class Transaction {
public:
void execute() {
_backup = _data; // 创建备份
try {
// 可能失败的操作
modifyData();
commit();
} catch(...) {
rollback();
throw;
}
}
private:
Data _data;
Data _backup;
};
掌握C++类与对象的核心概念是成为高级C++开发者的基础。从简单的数据封装到复杂的资源管理,面向对象的思想贯穿始终。在实际开发中,建议多使用现代C++特性(如智能指针、移动语义等),它们能让代码更安全、更高效。