AI部署通用C++知识点--面试必知必会(part2)
C++语法特性
1. 什么是内存泄漏及其类型
什么是内存泄漏?
内存泄漏是指程序在动态分配内存后,由于疏忽或错误造成了程序未能释放掉不再使用内存的情况。内存泄露并非指内存在物理上的消失,而是应用程序分配段内存后,由于设计错误,失去来对该段内存的控制。虽然内存仍然存在,但程序无法再使用它,因此会导致系统性能下降。
内存泄漏的常见类型
-
堆内存泄漏(Heap Leak)
- 堆内存是通过
malloc
、new
等函数分配的内存。如果程序没有正确释放这些内存,就会发生堆内存泄漏。
- 堆内存是通过
-
系统资源泄漏(Resource Leak)
- 系统资源(如文件句柄、socket、位图等)未能及时释放,导致系统资源浪费,影响系统的稳定性和性能。
-
基类析构函数未定义为虚函数
- 如果基类的析构函数不是虚函数,程序会失去对派生类资源的正确释放,导致内存泄漏。
举例:
class Base { public: ~Base() { cout << "Base destructor called" << endl; } }; class Derived : public Base { public: ~Derived() { cout << "Derived destructor called" << endl; } }; int main() { Base* basePtr = new Derived(); delete basePtr; // 错误:基类的析构函数不是虚函数,只会调用Base的析构函数 return 0; }
解决方法:将基类的析构函数声明为虚函数,确保派生类的资源也能正确释放。
class Base { public: virtual ~Base() { // 虚析构函数 cout << "Base destructor called" << endl; } };
-
释放对象数组时使用了
delete
而不是delete[]
- 如果使用
delete
来释放对象数组,只有数组的第一个元素会被析构,导致剩余的元素没有被释放,从而发生内存泄漏。
- 如果使用
-
缺少拷贝构造函数
- 如果没有正确实现拷贝构造函数,拷贝对象时可能会丢失对内存的控制,从而发生内存泄漏。
举例:
class MyClass { public: char* data; MyClass(const char* str) { data = new char[strlen(str) + 1]; strcpy(data, str); } MyClass(const MyClass& other) { // 默认浅拷贝 data = other.data; // 直接拷贝指针,未分配新内存 } ~MyClass() { delete[] data; // 错误:双重释放 } };
解决方法:实现深拷贝构造函数,确保每个对象拥有独立的内存空间。
MyClass(const MyClass& other) { data = new char[strlen(other.data) + 1]; strcpy(data, other.data); }
如何判断是否存在内存泄漏?
- 使用工具如 Valgrind(Linux 环境)来检测内存泄漏。
- 通过手动添加内存分配和释放的统计功能,来检查是否存在内存泄漏。
如何解决内存泄漏?
- 严格管理内存的申请与释放。
- 使用 智能指针(如
std::unique_ptr
或std::shared_ptr
),它们会在超出作用域时自动释放内存,减少内存泄漏的风险。
2. new/delete
与 malloc/free
的区别
new
和 delete
的特点
- C++ 关键字:
new
和delete
是 C++ 的关键字,支持重载。 - 自动调用构造函数与析构函数:
new
和delete
会分别调用对象的构造函数和析构函数,而malloc
和free
只会分配和释放内存,不涉及对象的构造与析构。 - 内存计算:
malloc
需要显式计算内存的大小,而new
会自动计算。 - 返回类型:
malloc
返回void*
指针,需要强制转换,而new
返回特定类型的指针。
malloc
和 free
的特点
- C 语言库函数:
malloc
和free
是 C 语言的标准库函数,不能重载。 - 不调用构造与析构函数:
malloc
和free
只处理内存的分配与释放,不处理对象的生命周期。
3. 运行时多态和虚函数
多态的概念
多态是指同一操作作用于不同的对象时,可以产生不同的结果。主要分为静态多态和动态多态。
静态多态(编译时多态)
- 函数重载:同名函数根据参数不同进行区分。
- 模板:通过泛型编程实现的多态。
动态多态(运行时多态)
动态多态依赖于 虚函数,实现的关键点在于:基类指针或引用指向派生类对象,通过基类指针调用虚函数时,实际调用的是派生类的实现,而非基类的实现。
示例代码:
class Animal {
public:
virtual void speak() { cout << "动物在叫" << endl; }
};
class Dog : public Animal {
public:
void speak() override { cout << "汪汪汪" << endl; }
};
int main() {
Animal* ptr = new Dog();
ptr->speak(); // 输出:汪汪汪
return 0;
}
虚函数的工作原理
- 每个含有虚函数的类有一个虚函数表(vtable),其中存储虚函数的地址。
- 对象通过 虚函数指针(vptr) 访问虚函数表,实现运行时的动态绑定。
4. 哈希表原理与冲突解决
哈希表的基本原理
哈希表通过 哈希函数 将数据的关键字映射到固定大小的数组中。哈希函数根据关键字计算出哈希值,并将数据存储在对应的数组位置。
常用的哈希函数:
- 数字分析法
- 平方取中法
- 分段叠加法
- 除留余数法
- 伪随机数法
哈希冲突及解决方法
哈希冲突 是指多个元素经过哈希函数计算后,映射到同一个位置。常用的冲突解决方法有:
- 开放地址法(再散列法):如果当前位置被占用,通过线性探测、二次探测等方法寻找空位。
- 链式地址法:使用链表存储哈希表中的元素,冲突的元素以链表的形式存储在同一个数组槽中。
- 建立公共溢出区:使用一个公共区域来存储冲突的数据。
- 再哈希表:当哈希表达到一定的负载因子时,重新分配内存并将数据重新哈希。
5. sizeof
与 strlen
的区别
sizeof
详解
sizeof
是编译时运算符,用于计算数据类型或变量的内存大小。它可以用于任何数据类型,不仅限于字符串。
示例:
char str1[100] = "hello";
cout << sizeof(str1); // 输出 100
strlen
详解
strlen
是一个运行时函数,用于计算 C 风格字符串的实际长度(不包括终止符 \0
)。
示例:
char str[] = "hello";
cout << strlen(str); // 输出 5
6. C++ 字符串:std::string
与 C 风格字符串
C 风格字符串与 std::string
的区别
-
C 风格字符串:C 风格字符串是以字符数组形式存储的字符串,通常以
\0
(空字符)结束。使用strlen
可以计算其长度,但需要手动管理内存,容易引发内存泄漏或越界错误。示例:
char str[50] = "Hello, world!"; cout << strlen(str); // 输出 13
-
std::string
:std::string
是 C++ 提供的标准库类,自动管理内存,并提供许多便捷的成员函数,如.length()
、.size()
等,能有效避免内存管理错误。示例:
#include <iostream> #include <string> using namespace std; int main() { string str = "Hello, world!"; cout << str.length(); // 输出 13 return 0; }
使用 std::string
的优势
- 自动内存管理:
std::string
会自动调整大小,处理内存分配和释放,避免了 C 风格字符串中需要手动管理内存的麻烦。 - 安全性:
std::string
提供了越界检查,减少了访问无效内存的风险。 - 丰富的功能:
std::string
提供了许多方便的成员函数,例如.substr()
、.find()
、.append()
等,极大简化了字符串的操作。
例子:使用 std::string
进行字符串操作
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "Hello, world!";
// 截取字符串
string subStr = str.substr(7, 5); // 从索引7开始,长度为5
cout << subStr << endl; // 输出 "world"
// 查找子串
size_t pos = str.find("world");
if (pos != string::npos) {
cout << "Found 'world' at position: " << pos << endl;
}
// 拼接字符串
str.append(" Welcome!");
cout << str << endl; // 输出 "Hello, world! Welcome!"
return 0;
}
总结
本文深入探讨了 C++ 中内存泄漏的常见原因以及如何避免,包括基类析构函数没有声明为虚函数、缺少拷贝构造函数等常见问题,并通过实例说明了如何避免这些问题。
此外,我们也讨论了 new/delete
与 malloc/free
的区别,解释了如何通过虚函数实现运行时多态,并介绍了哈希表的基本原理和冲突解决方法。最后,我们分析了 sizeof
和 strlen
的区别,C++ 字符串
与C 风格字符串
的区别,以帮助你更好地理解这些常用的 C++ 操作。
希望对你有帮助,未完待续
更多推荐
所有评论(0)