1、统一初始化
传统初始化方式:
// 传统初始化方式大观园 🎪
int a = 1; // 复制初始化 📝 - 像复印一样简单// 最常见但可能有性能开销 ⚠️int b(2); // 直接初始化 🎯 - 直接命中目标// 性能较好但写法不够直观 🤔int arr[] = {1, 2, 3}; // 数组聚合初始化 📦 - 把值打包在一起// 只能用于简单数据类型和POD类型 📌std::vector<int> v(3, 1); // 容器构造初始化 🏗️ - 建造者模式// 创建包含3个值为1的元素 🔢// 但这种语法容易与函数声明混淆 ⚠️// 每种初始化方式都像是不同风格的衣服 👔👕👗
// 虽然都能穿,但看起来不够统一!
就像每个学生穿自己喜欢的衣服上学,虽然也能用,但看着就很乱! 这些不同的初始化方式各有特点,但缺乏一致性,增加了学习和使用的难度 🎓 正是这种混乱促使了C++11引入统一初始化语法。
C++11统一初始化:
// 🎯 统一使用花括号{}初始化 - 就像所有学生都穿上了同样的校服// 基础类型初始化 🔰
int a{1}; // 像是给新生发的第一件校服
double d{3.14}; // 🎯 精确数值的初始化// 数组初始化 👥
int arr[]{1, 2, 3}; // 就像排队的学生们,整整齐齐站成一排
char str[]{"Hello"}; // 📝 字符数组也可以这样初始化// 容器初始化 📦
std::vector<int> v{3, 1}; // 像是把学生分到不同的班级里
std::array<int, 3> arr{1, 2, 3}; // 🎨 固定大小数组的初始化// 自定义类型初始化 🏗️
struct Point {int x;int y;
};
Point p{10, 20}; // 🎯 结构体成员一目了然// 嵌套初始化 📦
std::vector<Point> points{ // 🎁 复杂数据结构的优雅初始化{1, 2},{3, 4},{5, 6}
};
简要总结: 统一初始化就像是给所有变量都穿上了统一的"校服",不管是基本类型、数组还是复杂容器,都可以用花括号{}
来初始化。这种方式不仅让代码更整洁,还提供了类型安全检查,防止意外的数据丢失。记住:统一初始化是现代C++编程的推荐实践!💪
🌟 好处:
语法统一 - 到处都能用相同的{}形式
更安全 - 能防止意外的类型转换
更直观 - 一眼就能看出是在初始化
更灵活 - 适用于各种数据类型
统一初始化的"安全带":统一初始化不仅让代码更整洁,还自带"安全带"功能
// 🎯 更多安全检查的实战示例
struct Point {int x, y;
};// 🚫 以下初始化都会在编译时报错
longlong bigNum{3.14}; // ❌ 浮点数到整数的精度丢失
char ch{500}; // ❌ 超出char范围的数值
Point p{1, 2, 3}; // ❌ 参数过多
std::vector<int> v{10.5}; // ❌ 容器元素类型不匹配// ✅ 正确的初始化方式
longlong safeNum{314}; // 整数初始化没问题
char safeCh{'A'}; // 字符范围内的值
Point safeP{1, 2}; // 参数个数匹配
std::vector<int> safeV{10}; // 元素类型匹配
要点总结:
-
🛡️ 统一初始化就像编译时的安全检查员
-
🚫 防止有损的类型转换
-
⚠️ 阻止数组越界初始化
-
✨ 确保类型匹配和参数正确
-
💪 帮助写出更健壮的代码
记住:宁可在编译时发现错误,也不要在运行时遇到意外
聪明但有点"任性"的特性
不过要注意,统一初始化有时候会像个"任性的孩子",特别是遇到std::initializer_list
的时候:
class MagicBox {
public:// 🎁 普通的礼盒包装 - 接受两个简单的数字MagicBox(int x, int y) {} // 📦 特殊的礼盒包装 - 可以装入任意数量的数字// 🔍 当遇到{}初始化时,这个构造函数会被优先选择!MagicBox(std::initializer_list<int> list) {}
};// 👉 两种不同的初始化方式
MagicBox box1(10, 20); // ✅ 乖乖调用普通构造函数// 💡 明确指定使用两个参数的构造函数MagicBox box2{10, 20}; // 🎯 调皮地选择了列表构造函数 // ⚠️ 注意:这里的{10, 20}会被当作initializer_list// 🤔 可能不是你想要的行为!// 🎓 更多关于initializer_list的示例
class SmartBox {
public:// 👥 多个构造函数的情况SmartBox(int x) { } // ⚡️ 单参数构造函数SmartBox(std::initializer_list<int> list) { } // 📦 列表构造函数SmartBox(int x, int y, int z) { } // 🎲 三参数构造函数// 🔄 下面的初始化会调用哪个构造函数呢?// SmartBox b1{1}; // 📦 调用initializer_list构造函数// SmartBox b2(1); // ⚡️ 调用单参数构造函数// SmartBox b3{1,2,3}; // 📦 调用initializer_list构造函数// SmartBox b4(1,2,3); // 🎲 调用三参数构造函数
};
要点提示:
-
当类同时具有普通构造函数和
initializer_list
构造函数时 -
使用
{}
初始化会优先选择initializer_list
构造函数 -
使用
()
初始化则会选择最匹配的普通构造函数 -
这种行为可能导致意想不到的结果,需要特别注意! 🚨
🌟 最佳实践:
如果想明确调用普通构造函数,使用()
如果需要列表初始化的语义,使用{}
在设计类时要慎重考虑是否提供initializer_list
构造函数
使用建议 💡
记住:统一初始化是个好帮手,但不是万能药。用对了它,代码会更清晰、更安全!
-
统一初始化就像一件好工具:
-
✅ 需要安全检查时,用它!
-
✅ 想要代码整洁时,用它!
-
⚠️ 但遇到
initializer_list
时要小心 -
⚠️ 不要盲目使用,要根据具体场景选择
2、智能指针
智能指针是C++中的一种特殊类型指针,它提供了更加灵活和安全的内存管理功能。以下是对智能指针的详细概述:
一、定义与工作原理
智能指针是一个类,它封装了一个裸指针(即指向动态分配对象的指针),并自动管理该指针指向的内存资源。其工作原理是将动态分配的内存块与一个或多个智能指针对象相关联,以确保内存块在不再需要时能够自动释放。智能指针通过重载指针相关的操作符(如*、->等)来模拟指针的行为,同时在其析构函数中释放所管理的内存资源。
二、主要特点
自动管理内存:智能指针可以根据需要自动管理内存,避免了手动管理内存时的繁琐和错误。这使得编写代码更加简单、灵活和易于维护。
提高安全性:智能指针会自动检测指针的使用情况,如果指针被非法访问(如悬空指针),则会触发异常或进行其他安全处理。这避免了内存泄漏和悬挂指针等问题,提高了代码的安全性。
支持多种所有权模型:智能指针可以支持独占所有权(如std::unique_ptr)和共享所有权(如std::shared_ptr)等不同的所有权模型。独占所有权意味着只有一个智能指针可以拥有该内存资源,而共享所有权则允许多个智能指针共享同一个内存资源。
异常安全性:在异常发生时,智能指针能够确保资源的正确释放,从而避免了资源泄漏的问题。
三、常见类型
unique_ptr:独占所有权的智能指针,保证同一时间只有一个,可以指向某个对象。当unique_ptr被销毁时,它所管理的对象也会被销毁。
shared_ptr:共享所有权的智能指针,允许多个shared_ptr指向同一个对象。当没有任何shared_ptr指向该对象时,对象才会被销毁。shared_ptr使用引用计数来跟踪有多少个shared_ptr指向同一个对象。
weak_ptr:一种不控制对象生命周期的智能指针,它可以从一个shared_ptr或另一个weak_ptr创建,用于解决shared_ptr之间的循环引用问题。
auto_ptr(已废弃):C++98和C++03标准中的智能指针,但由于其设计上的缺陷(如所有权语义不明确),已在C++11中被废弃。
四、使用场景
智能指针广泛应用于需要动态分配内存的场景中,如资源管理、异常安全、循环引用解决、共享所有权实现以及自定义资源管理等。使用智能指针可以简化内存管理的代码,提高代码的安全性和可读性。
五、注意事项
避免悬空指针:不要将一个智能指针的裸指针赋给另一个裸指针,因为这会导致智能指针失去对该内存资源的控制,从而产生悬空指针。
正确传递和返回智能指针:在函数参数和返回值中使用智能指针时,要确保传递和返回方式正确,以避免不必要的资源泄漏或重复释放。
注意循环引用:在使用std::shared_ptr时,要注意避免循环引用的问题。可以使用std::weak_ptr来解决循环引用的问题。
综上所述,智能指针是C++中一个非常重要的特性,它提供了更加灵活和安全的内存管理方式。通过合理使用智能指针,可以简化内存管理的代码,提高代码的安全性和可读性。
面试官:你对智能指针有哪些了解?
应聘者:智能指针是一种自动化的内存管理工具,用于防止内存泄漏和悬空指针。C++11提供了三种智能指针:unique_ptr、shared_ptr和weak_ptr。unique_ptr只能有一个所有者,shared_ptr可以有多个所有者并使用引用计数,weak_ptr用于防止shared_ptr循环引用。
面试官:智能指针能保证对象不会内存泄漏吗?
应聘者:不能完全保证。如果存在循环引用,shared_ptr也会造成内存泄漏,这时需要使用weak_ptr。
智能指针:使用C++11及更高版本中的智能指针(如std::unique_ptr和std::shared_ptr)可以自动管理内存,减少内存泄漏的风险。
防止内存泄漏的策略
9、智能指针,unique_ptr、shared_ptr、weak_ptr,如何解决循环引用的问题
10、unique_ptr和shared_ptr的使用场景,优劣之分
- 智能指针?shared-ptr怎么实现、weak-ptr和它的区别
shared_ptr:共享指针,底层是依赖一个引用计数的原则
unique_ptr:
weak_ptr
weak_ptr的应用场景,是用来解决什么问题的
智能指针unique_ptr:单一所有者。shared_ptr:多个所有者,引用计数。weak_ptr:防止shared_ptr循环引用。
一下智能指针,make_shared
11、shared_ptr 是否线程安全(原子操作)
12、weak_ptr 是怎么知道所指向的对象是否还存在
智能指针shared_ptr是怎么实现的?怎么管理对象的?
详细 下智能指针的实现原理,内部计数器是怎么样实现的?(这个不会我说是信号量,面试官解答是atomic)
36.share-ptr能多线程吗?有什么用?
智能指针线程安全吗?对于数据的操作?
在多线程环境中,智能指针会出现问题吗?有可能一个指针被析构多次吗?
weak_ptr是用来解决什么问题的?
建议把所有指针都替换成智能指针吗?为什么?
如果在项目中禁用智能指针,怎么去做自动内存管理?
2. C++实现一个智能指针;智能指针的计数怎么实现
智能指针的底层了解吗?如何实现引用计数加一和减一的?
5.为什么使用智能指针?
自动内存管理:智能指针自动管理内存,不需要显式调用 delete,避免了内存泄漏和悬挂指针问题。
异常安全:智能指针能够确保即使发生异常,内存也能被正确释放,避免了因为异常导致的资源泄漏。
简化代码:智能指针让代码更简洁,不需要手动管理内存释放,使得资源管理更直观和安全。
3、auto关键字
auto 关键字(C++11)(了解即可)
· auto 关键字会根据初始化表达式推导出变量的类型,减少冗长的类型声明。
例子:
auto x = 5; // auto 推导出 x 是 int 类型
auto y = 3.14; // auto 推导出 y 是 double 类型
(10)C++11里面的移动构造的原理,和使用原理