C++ 类和对象知识点总结
一、类的默认成员函数
1. 六大默认成员函数
- 构造函数:初始化对象
- 析构函数:清理资源
- 拷贝构造函数:对象初始化对象
- 赋值运算符重载:对象赋值对象
- 取地址重载:普通对象和const对象
- 移动构造/赋值(C++11):资源转移
2. 默认生成规则
- 如果没有显式定义,编译器会自动生成默认成员函数
- 生成的默认函数执行浅拷贝(值拷贝)
- 如果类中有指针成员或资源管理,需要显式定义
二、构造函数
1. 特性
- 函数名与类名相同
- 无返回值
- 对象实例化时自动调用
- 可以重载
- 支持默认参数
2. 初始化列表
cpp
class Date {
public:
Date(int year, int month, int day)
: _year(year), _month(month), _day(day) {}
private:
int _year;
int _month;
int _day;
};
3. 必须使用初始化列表的情况
- 引用成员变量
- const成员变量
- 没有默认构造函数的类类型成员
- 自定义类型成员(提高效率)
cpp
class Example {
public:
Example(int& ref, int val)
: _ref(ref), _const(val) {} // 引用和const必须初始化
private:
int& _ref; // 引用成员
const int _const; // const成员
};
4. explicit关键字
- 防止隐式类型转换
- 禁止单参数构造函数的隐式转换
cpp
class String {
public:
explicit String(int n) { /* 分配n个字节 */ }
};
String s1(10); // 正确:显式调用
String s2 = 10; // 错误:explicit禁止隐式转换
三、析构函数
1. 特性
- 函数名:
~类名 - 无参数、无返回值
- 对象生命周期结束时自动调用
- 一个类只能有一个析构函数
2. 作用
- 释放对象申请的资源
- 自动调用,防止内存泄漏
- 先构造的后析构(栈的后进先出)
cpp
class Stack {
public:
Stack(int capacity) {
_data = new int[capacity];
}
~Stack() {
delete[] _data; // 释放动态内存
_data = nullptr;
}
private:
int* _data;
};
3. 需要显式定义的情况
- 类中有动态内存分配(
new/malloc) - 类中有文件操作、网络连接等资源
四、拷贝构造函数
1. 定义形式
cpp
ClassName(const ClassName& obj);
2. 特性
- 第一个参数必须是类类型对象的引用
- 推荐加const修饰(防止修改、支持临时对象)
- 拷贝构造用于对象初始化对象
3. 调用场景
cpp
void func(Date d) {} // 值传递参数
Date d1(2024, 1, 1);
Date d2(d1); // 场景1:对象初始化对象
Date d3 = d1; // 场景2:对象初始化对象(本质是拷贝构造)
func(d1); // 场景3:值传递参数
4. 深拷贝 vs 浅拷贝
浅拷贝问题示例:
cpp
class String {
public:
String(const char* str) {
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
~String() {
delete[] _str;
}
private:
char* _str;
};
String s1("hello");
String s2(s1); // 浅拷贝:s1和s2指向同一块内存,析构时重复释放!
深拷贝解决方案:
cpp
class String {
public:
String(const char* str) {
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
// 深拷贝构造
String(const String& s) {
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
~String() {
delete[] _str;
}
private:
char* _str;
};
五、赋值运算符重载
1. 定义形式
cpp
ClassName& operator=(const ClassName& obj);
2. 特性
- 必须是成员函数
- 推荐返回引用(支持连续赋值)
- 推荐加const修饰参数
- 用于已存在对象之间的赋值
3. 经典实现
cpp
class String {
public:
String& operator=(const String& s) {
if (this != &s) { // 防止自赋值
delete[] _str; // 释放旧资源
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
return *this; // 返回引用支持连续赋值
}
private:
char* _str;
};
4. 与拷贝构造的区别
cpp
String s1("hello");
String s2(s1); // 拷贝构造:s2还不存在
String s3;
s3 = s1; // 赋值重载:s3已存在
六、const成员
1. const成员函数
cpp
class Date {
public:
void Print() const { // const修饰this指针
cout << _year << "-" << _month << "-" << _day << endl;
// _year = 2025; // 错误:不能修改成员变量
}
private:
int _year, _month, _day;
};
2. 规则
- const成员函数不能调用非const成员函数
- 非const成员函数可以调用const成员函数
- const对象只能调用const成员函数
cpp
class Test {
public:
void Func1() { cout << "非const函数" << endl; }
void Func2() const {
cout << "const函数" << endl;
// Func1(); // 错误:const函数不能调用非const函数
}
};
int main() {
const Test t;
t.Func2(); // 正确:const对象调用const函数
// t.Func1(); // 错误:const对象不能调用非const函数
}
3. 建议
- 成员函数如果不修改成员变量,建议加const
- 提高程序健壮性,明确函数意图
七、取地址重载
1. 普通取地址 & const取地址
cpp
class Date {
public:
Date* operator&() {
cout << "普通取地址" << endl;
return this;
}
const Date* operator&() const {
cout << "const取地址" << endl;
return this;
}
};
int main() {
Date d1;
const Date d2;
cout << &d1 << endl; // 调用普通取地址
cout << &d2 << endl; // 调用const取地址
}
2. 说明
- 一般不需要显式定义
- 可以控制是否让外界获取对象地址
八、static成员
1. 静态成员变量
- 属于类,不属于对象
- 所有对象共享
- 必须在类外初始化
- 访问方式:
ClassName::变量名或对象.变量名
cpp
class Student {
public:
Student() { _count++; }
static int _count; // 静态成员变量声明
};
int Student::_count = 0; // 类外初始化
int main() {
Student s1, s2, s3;
cout << Student::_count << endl; // 输出:3
}
2. 静态成员函数
- 没有this指针
- 只能访问静态成员
cpp
class Student {
public:
static int GetCount() { // 静态成员函数
return _count; // 只能访问静态成员
}
private:
static int _count;
};
3. 应用场景
- 统计创建对象的个数
- 实现单例模式
- 共享数据
九、友元
1. 友元函数
cpp
class Date {
friend ostream& operator<<(ostream& out, const Date& d);
private:
int _year, _month, _day;
};
ostream& operator<<(ostream& out, const Date& d) {
out << d._year << "-" << d._month << "-" << d._day;
return out;
}
- 可以访问类的私有成员
- 不属于类,没有this指针
- 破坏封装,谨慎使用
2. 友元类
cpp
class A {
friend class B; // B是A的友元类
private:
int _data;
};
class B {
public:
void Func(A& a) {
cout << a._data << endl; // 可以访问A的私有成员
}
};
- 友元类的所有成员函数都是友元函数
- 友元关系是单向的,不可传递
十、内部类
1. 特性
- 定义在类内部的类
- 内部类默认是外部类的友元
- 外部类不是内部类的友元
cpp
class Outer {
public:
class Inner {
public:
void Func(Outer& o) {
cout << o._data << endl; // 内部类可访问外部类私有成员
}
};
private:
int _data;
};
2. 访问规则
- 内部类可以访问外部类的所有成员
- 外部类不能直接访问内部类的私有成员
十一、匿名对象
1. 特性
- 生命周期只在这一行
- 执行完立即调用析构函数
cpp
class Test {
public:
Test() { cout << "构造" << endl; }
~Test() { cout << "析构" << endl; }
};
int main() {
Test(); // 匿名对象:构造后立即析构
Test().Func(); // 调用成员函数
cout << "main" << endl;
}
// 输出:构造 析构 main
十二、移动构造与移动赋值(C++11)(拓展)
1. 为什么需要移动语义
cpp
String GetString() {
String s("hello");
return s; // 传统方式:深拷贝,效率低
}
- 传统拷贝构造需要分配新内存、复制数据
- 移动语义可以"偷走"临时对象的资源
2. 移动构造
cpp
class String {
public:
// 移动构造
String(String&& s) noexcept
: _str(s._str) { // 直接窃取资源
s._str = nullptr; // 源对象置空,防止重复释放
}
private:
char* _str;
};
3. 移动赋值
cpp
String& operator=(String&& s) noexcept {
if (this != &s) {
delete[] _str; // 释放自己的资源
_str = s._str; // 窃取对方资源
s._str = nullptr; // 对方置空
}
return *this;
}
4. std::move
cpp
String s1("hello");
String s2(std::move(s1)); // 显式调用移动构造
// s1此时为空,不要再用
5. 规则(移动语义五法则)
如果需要定义以下任一函数,通常需要全部定义:
- 析构函数
- 拷贝构造函数
- 拷贝赋值运算符
- 移动构造函数
- 移动赋值运算符
十三、=default 与 =delete(C++11)
1. =default
显式要求编译器生成默认实现:
cpp
class Date {
public:
Date() = default; // 显式使用编译器生成的默认构造
Date(int y, int m, int d);
};
2. =delete
禁止生成特定函数:
cpp
class NonCopyable {
public:
NonCopyable(const NonCopyable&) = delete; // 禁止拷贝构造
NonCopyable& operator=(const NonCopyable&) = delete; // 禁止拷贝赋值
};
NonCopyable a;
NonCopyable b(a); // 错误:已删除
3. 应用场景
cpp
// 禁止对象被复制(如单例模式)
class Singleton {
public:
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
// 禁止某些参数类型的函数调用
void func(int x);
void func(double) = delete; // 禁止double参数
func(10); // 正确
func(3.14); // 错误:已删除
十四、this 指针
1. 特性
- 每个非静态成员函数都有隐式的
this指针参数 this指向调用该函数的对象this是指针,用->访问成员
cpp
class Date {
public:
void Print() {
cout << this->_year << endl; // this指向调用对象
}
Date& GetSelf() {
return *this; // 返回对象本身的引用
}
};
2. this指针的类型
cpp
class Test {
void func(); // this类型: Test* const
void func() const; // this类型: const Test* const
};
- 普通成员函数:
类名* const this(指针本身不能改) - const成员函数:
const 类名* const this(指向内容也不能改)
3. 典型应用
cpp
// 链式调用
class Chain {
public:
Chain& setA(int a) { _a = a; return *this; }
Chain& setB(int b) { _b = b; return *this; }
private:
int _a, _b;
};
Chain c;
c.setA(1).setB(2); // 链式调用
十五、成员变量初始化顺序
1. 重要规则
成员变量按声明顺序初始化,而非初始化列表顺序!
cpp
class Test {
public:
Test(int x)
: _b(x), _a(_b) {} // 警告:_a可能未初始化!
private:
int _a; // 先声明,先初始化
int _b; // 后声明,后初始化
};
2. 正确写法
cpp
class Test {
public:
Test(int x)
: _a(x), _b(x) {} // 不依赖其他成员
private:
int _a;
int _b;
};
十六、mutable 关键字(拓展)
1. 作用
允许在 const 成员函数中修改特定成员变量
cpp
class Counter {
public:
int GetValue() const {
_count++; // mutable成员可以在const函数中修改
return _value;
}
private:
int _value = 0;
mutable int _count = 0; // 记录GetValue调用次数
};
2. 应用场景
- 调试计数器
- 缓存计算结果
- 线程同步锁(mutex)
十七、空类的大小
1. 为什么空类占1字节
cpp
class Empty {};
cout << sizeof(Empty); // 输出:1
- C++要求每个对象必须有唯一地址
- 如果大小为0,数组中的对象无法区分
- 编译器会插入1字节的占位符
2. 带成员变量的类
cpp
class Test {
int a; // 4字节
char b; // 1字节
// 内存对齐后,sizeof(Test) = 8
};
十八、拷贝省略(RVO/NRVO)
1. RVO(Return Value Optimization)
cpp
String GetString() {
return String("hello"); // 编译器优化:直接在调用处构造
}
String s = GetString(); // 可能只有一次构造,无拷贝
2. NRVO(Named RVO)
cpp
String GetString() {
String s("hello");
return s; // 编译器优化:s直接在调用处构造
}
3. C++17 保证
C++17规定在某些情况下必须省略拷贝:
cpp
T x = T(T(T())); // C++17保证只有一次构造
重点记忆
- 默认成员函数只做浅拷贝,涉及资源管理需显式定义
- 构造函数初始化列表是初始化成员变量的最佳方式
- 拷贝构造 vs 赋值重载:区分对象初始化和已存在对象赋值
- const成员函数提高程序健壮性,明确函数意图
- static成员属于类,所有对象共享
- 友元破坏封装,谨慎使用
- 深拷贝三要素:拷贝构造、赋值重载、析构函数