C++知识点思维导图
1. 拷贝构造函数
1.1. 拷贝构造函数传参及形参为 const 类&
的原因
拷贝构造函数用于用一个已存在的对象来初始化同类型的新对象。其传参形式通常为 类名(const 类名&)
。形参使用 const 类名&
原因如下:
- 避免不必要的拷贝:使用引用传递,不会创建实参对象的副本,提高效率。
- 防止误修改:
const
修饰确保在函数内部不会意外修改传入的对象。
1.2. 编译器自动补全的拷贝构造函数的工作
编译器自动生成的拷贝构造函数会逐个成员地将源对象的数据成员复制到目标对象。对于基本数据类型成员,直接复制值;对于对象成员,调用其拷贝构造函数(如果有)。
1.3. 手写拷贝构造函数的必要性
- 处理资源管理:当类中包含指针成员,指向动态分配的资源(如动态数组、文件句柄等)时,自动生成的拷贝构造函数是浅拷贝,会导致多个对象共享同一资源,析构时资源被多次释放或出现悬垂指针问题。手写拷贝构造函数可实现深拷贝,为新对象分配独立资源。
- 满足特殊业务逻辑:自动生成的拷贝构造函数只是简单成员复制,若类有特殊业务逻辑(如记录对象拷贝次数等),需手写实现。
1.4. 深拷贝与浅拷贝
- 浅拷贝: 仅复制对象中成员的值。对于指针成员,只是复制指针的值(即地址),新对象和原对象的指针指向同一块内存区域。这会导致多个对象共享资源,带来资源管理问题。
- 深拷贝:不仅复制对象中成员的值,对于指针成员,会重新分配内存,将原指针指向的内容复制到新分配的内存中,使新对象和原对象拥有各自独立的资源副本。
#include <iostream>
#include <cstring>
class MyClass {
private:
int* data;
public:
MyClass(int size) : data(new int[size]) {
std::cout << "Constructor called" << std::endl;
}
// 浅拷贝构造函数(错误示范)
MyClass(const MyClass& other) {
data = other.data;
std::cout << "Shallow Copy Constructor called" << std::endl;
}
// 深拷贝构造函数
MyClass(const MyClass& other) {
int size = other.getSize();
data = new int[size];
std::memcpy(data, other.data, size * sizeof(int));
std::cout << "Deep Copy Constructor called" << std::endl;
}
int getSize() const {
return data? sizeof(data) / sizeof(data[0]) : 0;
}
~MyClass() {
delete[] data;
std::cout << "Destructor called" << std::endl;
}
};
2. C++ 中的 static
2.1. static 静态存储区特点
- 生命周期:静态存储区的变量在程序启动时分配内存,程序结束时释放内存,其生命周期贯穿整个程序运行过程。
- 初始化:若未显式初始化,静态变量会被自动初始化为 0(对于数值类型)或空指针(对于指针类型)。
- 作用域:在定义它的作用域内有效,局部静态变量在函数内部定义,但生命周期不局限于函数调用期间。
2.2. 类里面的静态成员变量特点
- 共享性:无论类创建多少个对象,静态成员变量只有一份,被所有对象共享。
- 存储位置:存储在静态存储区,而非对象的内存空间中。
- 初始化:一般在类外进行初始化,格式为
数据类型 类名::静态成员变量名 = 初始值;
。 - 访问方式:可以通过类名直接访问(
类名::静态成员变量名
),也可以通过对象访问。
class MyClass {
private:
static int sharedData;
public:
static int getSharedData() {
return sharedData;
}
};
int MyClass::sharedData = 10;
2.3. 类里面的静态成员函数特点
- 无
this
指针:静态成员函数不属于任何一个具体对象,所以没有隐含的this
指针,不能直接访问非静态成员变量和非静态成员函数。 - 访问权限:遵循类的访问控制规则,可在类外通过类名或对象访问(公有静态成员函数 )。
- 用途:常用于提供与类相关的工具函数或访问静态成员变量。
class MyClass {
private:
static int sharedData;
public:
static int getSharedData() {
return sharedData;
}
};
int MyClass::sharedData = 10;
2.4. 类里面如果存在回调函数的处理
在类中使用回调函数时,由于静态成员函数没有 this
指针,若回调函数需要访问类的非静态成员,有以下解决方式:
- 将相关数据作为参数传递:把需要访问的非静态成员变量的值作为参数传递给静态回调函数。
- 使用
std::bind
或 lambda 表达式:std::bind
可以将对象的成员函数绑定到一个对象上,生成可调用对象;lambda 表达式可以捕获类对象,方便在回调中访问非静态成员。
#include <iostream>
#include <functional>
class MyClass {
private:
int data;
public:
MyClass(int value) : data(value) {}
void callback(int param) {
std::cout << "Callback: data = " << data << ", param = " << param << std::endl;
}
};
void generalCallback(void* obj, int param) {
MyClass* myObj = static_cast<MyClass*>(obj);
myObj->callback(param);
}
int main() {
MyClass obj(5);
auto boundCallback = std::bind(generalCallback, &obj, std::placeholders::_1);
boundCallback(10);
return 0;
}
3. new
和 malloc
的区别
- 所属类别:
new
是 C++ 的关键字,malloc
是 C 标准库函数(在 C++ 中也可用)。 - 内存分配与初始化:
new
在分配内存后会自动调用对象的构造函数进行初始化;malloc
仅分配指定大小的未初始化内存块,若用于对象,需手动调用构造函数。 - 返回值类型:
new
直接返回所需类型的指针,无需类型转换;malloc
返回void*
,使用时需进行类型转换。 - 内存释放方式:
new
搭配delete
释放内存,会调用对象的析构函数;malloc
搭配free
释放内存,不会调用析构函数。 - 异常处理:
new
在分配内存失败时会抛出std::bad_alloc
异常;malloc
分配失败时返回NULL
,需手动检查返回值判断是否分配成功。
#include <iostream>
#include <cstdlib>
class MyClass {
public:
MyClass() {
std::cout << "Constructor called" << std::endl;
}
~MyClass() {
std::cout << "Destructor called" << std::endl;
}
};
int main() {
// new 操作符
try {
MyClass* ptr1 = new MyClass;
delete ptr1;
} catch (const std::bad_alloc& e) {
std::cerr << "new failed: " << e.what() << std::endl;
}
// malloc 函数
MyClass* ptr2 = static_cast<MyClass*>(std::malloc(sizeof(MyClass)));
if (ptr2!= NULL) {
new (ptr2) MyClass; // 定位 new 手动调用构造函数
ptr2->~MyClass(); // 手动调用析构函数
std::free(ptr2);
} else {
std::cerr << "malloc failed" << std::endl;
}
return 0;
}
4. 单例模式
4.1. 懒汉单例的整体逻辑
懒汉单例模式是在第一次使用单例对象时才进行创建。其基本逻辑如下:
- 定义一个静态成员变量来存储单例对象的指针(或对象实例 )。
- 提供一个静态成员函数用于获取单例对象,在函数内部检查单例对象是否已创建,若未创建则进行创建,之后返回单例对象。
class Singleton {
private:
static Singleton* instance;
Singleton() {}
public:
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton;
}
return instance;
}
};
Singleton* Singleton::instance = nullptr;
4.2. 类里面如果要上锁(实现线程安全的懒汉单例)
为使懒汉单例在多线程环境下安全,可使用互斥锁(如 std::mutex
)。基本思路是在创建单例对象时加锁,防止多个线程同时创建对象。
#include <mutex>
class Singleton {
private:
static Singleton* instance;
static std::mutex mutex_;
Singleton() {}
public:
static Singleton* getInstance() {
std::lock_guard<std::mutex> guard(mutex_);
if (instance == nullptr) {
instance = new Singleton;
}
return instance;
}
};
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex_;
4.3. 饿汉单例的整个流程
饿汉单例模式在程序启动时就创建单例对象,无需考虑线程安全问题(因为在单线程初始化阶段创建 )。其流程如下:
- 定义静态成员变量并直接初始化单例对象。
- 提供获取单例对象的静态成员函数,直接返回已创建的对象。
class Singleton {
private:
static Singleton instance;
Singleton() {}
public:
static Singleton& getInstance() {
return instance;
}
};
Singleton Singleton::instance;