文章目录
1. 指针
1.1 数组和指针
数组指针是指针,指向数组的第一个元素;
#include <stdio.h>
int main() {
int arr[3] = {
1, 2, 3};
int (*ptr)[3] = &arr; // ptr 是一个指向包含3个整数的数组的指针
// 通过指针访问数组元素
for (int i = 0; i < 3; i++) {
printf("%d ", (*ptr)[i]);
}
printf("\n");
return 0;
}
指针数组是数组,数组的每个对象都是一个指针;
#include <stdio.h>
int main() {
int a = 10, b = 20, c = 30;
int *ptr_arr[3]; // ptr_arr 是一个包含3个整数指针的数组
ptr_arr[0] = &a;
ptr_arr[1] = &b;
ptr_arr[2] = &c;
// 通过指针数组访问整数变量
for (int i = 0; i < 3; i++) {
printf("%d ", *ptr_arr[i]);
}
printf("\n");
return 0;
}
1.2 函数指针
函数指针是一个指针,它指向一个函数的地址。函数指针允许我们通过指针允许我们通过指针来调用函数,这在实现回调函数调用时非常有用,虚函数表也就时一个函数指针数组。
#include <stdio.h>
// 定义一个函数类型
typedef int (*FuncPtr)(int, int);
// 定义一个函数
int add(int a, int b) {
return a + b;
}
// 另一个函数
int subtract(int a, int b) {
return a - b;
}
// 使用函数指针的函数
void useFunctionPointer(FuncPtr fp, int x, int y) {
int result = fp(x, y);
printf("Result: %d\n", result);
}
int main() {
FuncPtr fp;
// 指向 add 函数
fp = add;
useFunctionPointer(fp, 5, 3); // 输出: Result: 8
// 指向 subtract 函数
fp = subtract;
useFunctionPointer(fp, 5, 3); // 输出: Result: 2
return 0;
}
指针函数是一个返回指针的函数,函数的返回类型是指针。
#include <stdio.h>
#include <stdlib.h>
// 函数返回一个指向整数的指针
int* createArray(int size) {
int* array = (int*)malloc(size * sizeof(int)); // 动态分配内存
if (array == NULL) {
printf("Memory allocation failed\n");
exit(1);
}
// 初始化数组
for (int i = 0; i < size; i++) {
array[i] = i + 1;
}
return array;
}
int main() {
int size = 5;
int* arr = createArray(size);
// 打印数组内容
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// 释放分配的内存
free(arr);
return 0;
}
1.3 const 和 指针、static、#define、typedef
const
:定义变量不能修改,必须在定义时初始化;在*
前是常量指针(不能通过该指针修改),在*
后是指针常量(不能再指向其它地址)修饰函数的参数(函数体内不能修改这个值)#define
: C语言中定义语法,是预处理指令(简单的替换)、没有作用域typedef
:有类型检查功能,在定义很长类型的时候方便的多、有作用域#define
与const
都能定义常量,define
可替换常量、表达式(代码段中:不分配内存空间);const
常量在(数据段中)分配空间
#include <stdio.h>
// 加法宏函数
#define ADD(x, y) ((x) + (y))
// 减法宏函数
#define SUBTRACT(x, y) ((x) - (y))
int main() {
int a = 5;
int b = 3;
printf("Addition: %d + %d = %d\n", a, b, ADD(a, b)); // 输出: 5 + 3 = 8
printf("Subtraction: %d - %d = %d\n", a, b, SUBTRACT(a, b)); // 输出: 5 - 3 = 2
return 0;
}
1.4 指针和引用的异同
- 相同:都是地址概念;
- 区别:指针是实体、引用是别名;指针
++
是内存地址自增、引用时值自增;引用无需解引用(*
)、指针需要解引用、引用只能在定义的时候被初始化、引用不能为空。
1.5 sizeof与strlen
https://qhd666.blog.csdn.net/article/details/132915168
sizeof
用于计算数据类型或变量的大小(以字节为单位),它可以用于基本数据类型、数组、结构体、联合体以及其他类型。strlen
计算以null
终止字符的长度(不包括null
终止符),它在string.h
头文件中定义。- 数组所占空间:sizeof(数组名); 数组大小:sizeof(数组名)/sizeof(数据类型);
2. 库函数及其模拟实现
https://qhd666.blog.csdn.net/article/details/133102559
memcpy
用于不重叠的内存区域快速复制数据,而 memmove
用于处理可能重叠的内存区域,确保数据正确移动。
字符串拷贝:strcpy
、strncpy
字符创连结:strcat
、strncat
字符串比较:memcmp
、strcmp
、strncmp
字符串查找:strstr
计算长度:strlen
…
3. 自定义类型
内存对齐:自定义类型的内存对齐是确保数据在内存中按照特定字节边界排列,以提高访问效率和满足硬件要求。
https://qhd666.blog.csdn.net/article/details/134456752?spm=1001.2014.3001.5502
https://qhd666.blog.csdn.net/article/details/132009604?spm=1001.2014.3001.5502
- 内存对齐的规则:
- 第一个成员在与结构体变量偏移量为0的位置
- 其他成员变量要对齐到对齐数的整数倍位置
- 对齐数 = min( 编译器默认的对齐数 , 该成员变量大小 ) , VS下默认对齐数为8
- 结构体总大小为每一个成员变量的最大对齐数的整数倍
- 修改默认对齐数的方法: #pragma pack(8) , 设置默认对齐数为8
- 为什么要有内存对齐
- 为了平台的可移植:不是所有的硬件都能访问任意地址上的任意数据, 可能只能在4的倍数的地址处取数据。
- 为了性能:若内存不对齐, 原本只需要访问一次的数据现在可能要访问两次才能拿全
- 整型的存储:
- 原码:直接表示整数的符号和大小,最高位为符号位。
- 反码:正数与原码相同,负数的表示是将原码的每一位取反。
- 补码:正数与原码相同,负数的表示是将反码加1,主要用于简化计算和处理负数。
4. 数据存储
https://qhd666.blog.csdn.net/article/details/132009604
静态存储区(全局变量、静态变量)、栈区(局部变量、函数内参数)。
大端:高地址存低字节、低地址存高字节
小端:高地址存高字节、低地址存低字节
5. 编译链接过程
编译:将源代码翻译成目标代码(机器语言),生成一个或多个目标文件。
汇编:将汇编代码转换成目标代码(如果需要)。
链接:将多个目标文件和库文件合并,解决符号引用,生成最终的可执行文件。
6. C++入门基础
6.1 函数重载
https://qhd666.blog.csdn.net/article/details/140560876
函数重载是指在同一作用域内,多个函数名相同但参数列表不同的特性。
6.2 引用和指针
https://qhd666.blog.csdn.net/article/details/140904531?spm=1001.2014.3001.5502
- 指针和引用的异同:
相同:都是地址概念;
区别:指针是实体、引用是别名、指针++是内存地址自增、引用时值自增、引用无需解引用(*)、指针需要解引用、引用只能在定义的时候被初始化一次、引用不能为空。
- 引用价值:做参数和做返回值
引用可以作为参数传递给函数以避免复制开销,也可以作为返回值以允许函数返回对对象的直接引用。
例如:
int& getElement(std::vector<int>& vec, size_t index) {
return vec[index]; // 返回对 vec 中元素的引用
}
- 引用也不是绝对安全的:
悬挂引用:如果引用的对象在引用被使用之前已经被销毁或超出作用域(例如局部变量),那么引用将变成悬挂引用,导致未定义行为。
int* unsafeFunc() {
int local = 42;
return &local; // local 在函数返回后被销毁
}
// 访问 unsafeFunc() 返回的指针将是未定义行为
6.3 建议使用const、inline、enum去替代宏
- 宏的缺点:
- 调试困难:宏展开后,调试时无法直接查看宏的原始代码。
- 类型安全差:宏不进行类型检查,容易导致类型错误。
- 作用域问题:宏没有作用域限制,可能影响其他代码。
- 代码重复:宏可能导致重复代码,增加维护难度。
- 可能导致错误:宏展开中的括号问题可能引发难以发现的错误。
- inline要求:频繁调用短小函数合适(给编译器提建议)
6.4 nullptr的意义是什么?
在C++中 #define NULL 0
NULL
定义存在缺陷
nullptr
是 C++11 引入的类型安全关键字,用于表示空指针,而NULL
是一个宏定义,通常与0
等价,可能在类型安全和可移植性方面不如nullptr
。
7. 类和对象
7.1 面向对象和面向过程的区别?
面向对象侧重于对象的抽象和封装,而面向过程则着重于步骤和函数的顺序执行。
7.2 类大小的计算
-
内存对齐
-
空类
7.3 class和struct区别
在C++中,class
的成员默认是私有的,而struct
的成员默认是公有的。
7.4 C++11智能指针
https://qhd666.blog.csdn.net/article/details/139183793
unique_ptr
禁止拷贝、shared_ptr
使用引用计数:解决多次释放问题、每一个资源配一个引用计数
7.5 this指针
是每个对象的专属指针,访问自己的数据成员和方法。
this
指针存在哪里?
this
指针在类的成员函数中,指向当前对象的内存地址。它通常存储在栈上或寄存器中。this
指针可以为空吗?
this 指针不会为空,因为它总是指向当前对象。
7.6 八个默认成员函数
-
构造和析构
-
拷贝构造和赋值
-
移动构造和移动赋值
- 什么情况编译器可以生成移动构造和移动赋值?
编译器可以生成移动构造函数和移动赋值运算符,当类没有自定义这些函数,并且所有成员支持移动操作时。
拷贝构造、赋值、析构都没有写才会自动生成。
- 初始化列表
- 特性是什么?
以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。- 哪些成员必须在初始化列表初始化?
引用成员变量
const成员变量
自定义类型成员(且该类没有默认构造函数时)- 初始化顺序是按声明顺序
- 什么情况下要自己写?
一般构造都要自己写:
深拷贝的类,一般都要自己写析构、拷贝、构造、赋值。
深拷贝的类,也需要实现移动构造和移动赋值 - 默认生成的这些函数行为是什么?
- 默认构造函数:初始化对象的成员。
- 复制构造函数:逐个复制对象的成员。
- 赋值运算符:将右侧对象的成员赋值给左侧对象。
- 析构函数:释放对象占用的资源。
- 移动构造函数:移动资源,清空右侧对象。
- 移动赋值运算符:移动资源并清空右侧对象。
这些行为适用于所有类,除非你自定义了这些函数。
- 什么是默认构造?(不传参的都可以是默认构造)
我们不写,编译器默认生成的是默认构造。
全缺省的是默认构造 。
没有参数的是默认构造。
7.7 运算符重载
- 哪些运算符不能重载?
不能重载的运算符包括:
::
(作用域解析运算符)、.
(成员访问运算符)、.*
(成员指针访问运算符)、?:
(条件运算符)和sizeof
(大小运算符)。
- 和函数重载的区别?
运算符重载是为了改变运算符对自定义类型的操作方式,而函数重载是为了在同一作用域内定义多个功能相似但参数不同的函数。
- 它的意义是什么?
运算符重载的意义在于使自定义类型的操作更自然、直观,从而提高代码的可读性和简洁性。
7.8 友元
- 友元函数
- 友元类
7.9 static 成员
static
成员就是全局成员不占类的大小,仅仅只是受到类域限制而已,生命周期也是全局的。
7.10 对象拷贝时,编译器的优化
在对象拷贝时,编译器的优化通常包括使用移动语义来减少不必要的复制,以及应用拷贝省略(copy elision)来避免临时对象的拷贝。
8. 内存管理
8.1 内存分布
- 栈又叫堆栈–非静态局部变量/函数参数/返回值等等,栈是向下增长的。
- 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口
创建共享共享内存,做进程间通信。 - 堆用于程序运行时动态内存分配,堆是可以上增长的。
- 数据段–存储全局数据和静态数据。
- 代码段–可执行的代码/只读常量
8.2 malloc
、calloc
和 realloc
的区别:
malloc
:分配指定字节数的内存块,但不初始化其内容。返回指向该内存块的指针。
void* malloc(size_t size);
calloc
:分配内存并初始化为零。需要指定要分配的块数和每块的字节数。
void* calloc(size_t num, size_t size);
realloc
:重新调整已分配内存块的大小。如果需要,可以将内容复制到新位置,并释放原内存块
void* realloc(void* ptr, size_t size);
8.3 new/delete 和 malloc/free 的区别
new
和delete
是C++的操作符,用于对象的动态分配和释放,支持构造和析构函数的调用,确保对象的正确初始化和清理。malloc
和free
是C的函数,用于分配和释放内存,不会调用构造或析构函数,仅仅是内存的分配和释放。
8.4 new和new[]的底层实现原理
operator new
+ 构造函数
8.5 内存泄漏
- 什么是内存泄漏?
内存泄漏是指程序在运行过程中未释放不再使用的内存空间。
- 内存泄漏的危害是什么?
内存泄漏会导致系统内存资源枯竭,影响程序性能和稳定性。
- 如何解决内存泄漏?
解决内存泄漏需定期检查和释放不再使用的内存,并使用工具进行监控。
9. 模板
https://qhd666.blog.csdn.net/article/details/134654728
9.1 语法格式
9.2 模板的原理
C++模板的原理是通过编译时的类型推导和代码生成机制来实现类型安全和代码复用,使得相同的代码可以适用于不同的数据类型。
9.3 模板的特化
模板的特化是指为特定类型提供自定义实现的机制,使得在模板的默认实现不适用时,可以定义更适合特定情况的版本。
#include <iostream>
// 通用模板
template <typename T>
void printType() {
std::cout << "General template" << std::endl;
}
// 特化模板
template <>
void printType<int>() {
std::cout << "Specialized template for int" << std::endl;
}
int main() {
printType<double>(); // 调用通用模板
printType<int>(); // 调用特化模板
return 0;
}
在这个例子中:
printType<T>
是一个通用模板,适用于任何类型T
。printType<int>
是对printType
模板的特化,仅适用于int
类型。
运行时:
printType<double>()
会调用通用模板,输出 "General template"
。
printType<int>()
会调用特化模板,输出 "Specialized template for int"
。
10. 继承
- 什么是继承?
继承是面向对象编程中的一种机制,它允许一个类(子类)从另一个类(基类)继承属性和方法。
- 继承的意义是什么?
其意义在于实现代码重用和扩展功能。通过继承,子类可以继承基类的行为,同时还可以新增或修改自己的行为,使得程序结构更清晰、灵活。
- 赋值兼容——切片
在C++中,赋值兼容切片指的是将派生类对象赋给基类对象时,派生类的额外成员被丢弃,只保留基类部分。这可能导致丢失派生类特有的数据。
- 如何避免切片问题?
- 使用基类指针或引用:通过基类指针或引用处理派生类对象。
- 使用虚拟函数:让基类方法成为虚拟函数,以保证派生类的方法可以被调用。
- 隐藏
- 成员变量的隐藏
- 成员函数的隐藏——函数名相同且不构成重写就是隐藏
- 默认成员函数
子类要复用父类的
- 多继承
菱形继承:数据冗余和二义性
虚继承:虚继承解决数据冗余和二义性原理
虚基表:存的偏移量,距离a对象存的偏移量
建议不要设计菱形虚拟继承,代码可行性和控制难度都会上升。
- 继承和组合
继承和组合是面向对象编程中的两种主要的类关系,用于实现代码重用和功能扩展。
- 继承
- 定义:一个类(子类)继承自另一个类(基类),从而获得基类的属性和方法。
- 特点:
代码重用:子类可以重用基类的代码。
扩展性:子类可以新增或修改功能。
关系:表示“是一种”关系(例如,“猫”是“动物”)。
class Animal {
public:
void eat() {
}
};
class Dog : public Animal {
public:
void bark() {
}
};
- 组合
- 定义:一个类包含另一个类的对象作为其成员,实现功能复用。
- 特点:
灵活性:可以动态组合不同的类来实现复杂功能。
封装性:组合的类可以隐藏其内部实现细节。
关系:表示“拥有”关系(例如,“车”拥有“引擎”)。
class Engine {
public:
void start() {
}
};
class Car {
private:
Engine engine;
public:
void start() {
engine.start(); // 使用组合来启动引擎
}
};
选择:
- 继承适用于“是一个”关系,通常在类之间存在强关系时使用。
- 组合适用于“拥有”关系,通常在需要灵活配置和复用功能时使用。
组合耦合度低、继承耦合度高;优先用组合。
11. 多态
11.1 什么是多态?
- 静态多态:函数重载
- 动态多态:
- 虚函数重写
虚函数:子类中虚函数不能加virtual
三同:协变例外(返回值可以不同,但返回值必须是父子类的指针或者引用)- 父类指针或者引用去调用虚函数;
- 指向父类调父类,指向子类调子类
11.2 析构函数建议是虚函数?为什么?
析构函数应为虚函数,以确保通过基类指针删除对象时,派生类的析构函数也被调用,从而正确释放资源。
11.3 纯虚函数
包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象
间接强制子类重写,不重写,子类依旧是抽象类
11.4 重载/重写(覆盖)/隐藏(重定义)的区别
- 重载:同一作用域内,函数名相同但参数不同(不同类型或数量)。用于实现函数的多态性。
- 重写(覆盖):在派生类中重新定义基类中的虚函数,以改变或扩展其行为。确保基类指针或引用调用派生类的实现。
- 重写(覆盖):在派生类中重新定义基类中的虚函数,以改变或扩展其行为。确保基类指针或引用调用派生类的实现。
11.5 多态原理
- 虚函数重写以后,父类对象虚表指针指向的虚函数表存的是父类的虚函数。
- 虚函数重写以后,子类对象虚表指针指向的虚函数表存的子类重写的虚函数。
- 父类的指针或引用调用虚函数->去指向的对象中查找对象虚函数进行调用,最终达到了指向谁,调用谁
12. C++11
12.1 范围for
范围for语句提供了一种简洁的方式来遍历容器或范围内的元素。
12.2 右值引用的移动语意
- 什么是左值引用?什么是右指引用?
左值引用引用可修改的左值,而右值引用引用临时对象或即将被移动的右值。
- 右值引用的使用场景是什么?如何提高效率
深拷贝的类,传值返回的优化;
深拷贝的类,做参数;
- 移动构造和移动赋值
移动构造通过转移资源的所有权来初始化新对象,而移动赋值通过转移资源的所有权来赋值给已有对象。
- 完美转发
完美转发是一种将函数参数转发给另一个函数,同时保持其原始的值类别(左值或右值)的技术。
push
和emplace
系列的区别
push
在容器中添加一个元素,通常需要先创建该元素,然后传递给容器;
emplace
直接在容器内部构造元素,避免了额外的拷贝或移动操作。
12.3 lambda
- 用法
lambda 表达式是一种匿名函数,允许在需要时创建内联函数。它通过[捕获列表](参数列表) -> 返回类型 { 函数体 }
的语法定义,捕获外部变量,并在执行时调用。 - 原理
原理上,lambda 表达式生成一个闭包对象,该对象包含其捕获的变量和函数体。
闭包对象:是一个包含了捕获外部变量的lambda表达式的实例,它保证了这些变量的状态,并允许在lambda表达式中访问和操作这些变量。
12.4 function/bind
std::function
是一个通用的函数封装器,可以存储任何可调用对象,如函数、lambda
表达式和函数对象。std::bind
用于创建一个新的函数对象,通过绑定部分或全部参数来简化函数调用。std::function
使得函数调用变得灵活,而std::bind
允许部分参数预设。
12.5 多线程库
std::thread
:用于创建和管理线程。std::mutex
:提供互斥锁,保护共享资源以防数据竞争。std::condition_variable
:用于线程间的通知和等待机制,促进线程间的通信。std::atomic
:提供对基本数据类型的原子操作,确保线程安全。
12.6 STL中的一些变化
- 智能指针:引入
std::unique_ptr
和std::shared_ptr
,用于自动管理动态内存。 - 新容器:增加
std::unordered_map
和std::unordered_set
,基于哈希表实现。 - 并发支持:提供线程、互斥量、条件变量和原子操作等支持。
- 函数对象和绑定:引入
std::function
和lambda
表达式,支持更灵活的函数操作。 - 初始化列表:简化了数组和容器的初始化方式。
- std::tuple 和 std::tie:支持存储和解包多个值。
- 范围-based for 循环:提供更简洁的容器遍历方式。
- constexpr:允许在编译时计算常量表达式。
13. 异常
https://qhd666.blog.csdn.net/article/details/139156745
- 异常的用法:
- 抛出异常:使用
throw
关键字抛出异常对象。
throw std::runtime_error("Error occurred");
- 捕获异常:用
try
和catch
代码块捕获并处理异常。
try {
// Code that may throw
} catch (const std::exception& e) {
// Handle exception
}
noexcept
:标识函数不会抛出异常。
void func() noexcept;
- 标准异常类:如
std::exception
、std::runtime_error
、std::logic_error
等,用于表示不同类型的错误。
- 异常的优缺点:
优点:C++ 异常提供了一种清晰的错误处理机制,分离错误处理逻辑与主业务逻辑。
缺点:使用异常可能导致性能开销增加和代码复杂度提升。
14. 智能指针
https://qhd666.blog.csdn.net/article/details/139183793
14.1 发展历史
智能指针的发展历史始于 C++ 标准库的 std::auto_ptr
,随后被 std::unique_ptr
和 std::shared_ptr
所取代,以提供更安全、灵活的内存管理功能。
14.2 RAII
RAII(资源获取即初始化)是一种编程 idiom(习惯),通过将资源的管理(如内存、文件句柄)与对象的生命周期绑定,确保资源在对象创建时分配,在对象销毁时释放。
14.3 auto_ptr/unique_ptr/shared_ptr/weak_ptr特点是什么?解决了什么问题?
auto_ptr
:
- 特点:C++98 中引入,转移所有权(复制时失效)。
- 解决问题:自动管理内存,避免手动释放。
- 问题:易导致悬空指针,已在 C++11 中弃用。
unique_ptr
:
- 特点:C++11 引入,独占所有权,支持移动而不复制。
- 解决问题:避免资源泄漏和悬空指针,提供明确的所有权语义。
shared_ptr
:
- 特点:C++11 引入,多个实例共享同一资源,通过引用计数管理。
- 解决问题:确保资源在最后一个
shared_ptr
被销毁时释放,防止内存泄漏。
weak_ptr
:
- 特点:C++11 引入,与
shared_ptr
配合使用,不增加引用计数。- 解决问题:打破循环引用,允许观察对象而不管理其生命周期,避免内存泄漏。
14.4 模拟实现
14.5 什么是循环引用?怎么解决?
循环引用:循环引用是指对象间互相引用形成一个环,导致无法被垃圾回收机制释放,造成内存泄漏。
解决方法包括:使用弱引用(如std::weak_ptr),重新设计对象关系以避免直接循环引用,或手动管理对象生命周期。
14.6 定制删除器
定制删除器是一个功能,可以在智能指针(如
std::unique_ptr
)中指定自定义的函数或对象,用于定义对象销毁时的具体行为,比如释放特定资源或执行额外的清理操作。这使得在智能指针生命周期结束时,能够更灵活地管理资源释放。
15. 四种类型转换
static_cast
:用于进行编译时类型转换,适用于已知的、安全的转换,如基本数据类型之间的转换。dynamic_cast
:用于运行时类型转换,常用于类层次结构中基类指针或引用到派生类的安全转换,通常与虚函数配合使用。const_cast
:用于修改对象的常量性,即去除或添加const
或volatile
属性。reinterpret_cast
:用于进行低级别的类型转换,通常在不同类型的指针之间转换,但可能导致不安全的行为。
16. IO流
iostream
:基本的输入输出流库。
cin
:标准输入流。cout
:标准输出流。cerr
:标准错误流。clog
:日志流。
- 流操作符:
<<
:用于将数据写入输出流(如cout
)。>>
:用于从输入流中读取数据(如cin
)。
- 文件流:
ifstream
:用于从文件读取数据。ofstream
:用于向文件写入数据.fstream
:用于同时读写文件数据。
- 库的意义:
- 面向对象
- 支持自定义类型的流插入和提取
17. C++的优缺点
优点:
- 高性能:编译型语言,支持底层控制,适合性能敏感的应用。
- 灵活性:支持面向对象编程、泛型编程、手动内存管理。
- 标准库:提供丰富的数据结构、算法和功能(如STL)。
- 跨平台:代码可在不同平台上编译和运行。
- 兼容性:与 C 代码兼容,利用已有的 C 代码库。
缺点:
- 复杂性:语言特性多、语法复杂,学习曲线陡峭。
- 内存管理:手动管理内存,容易引发内存泄漏等问题。
- 编译时间:大型项目编译时间较长。
- 线程安全:标准库提供的多线程支持需要额外管理线程安全。
- 标准库更新慢:新特性引入较慢,可能不及时满足需求。
-------------------------------------------未完待续----------------------------------------------------