值传递、引用传递、指针传递的区别
值传递:将实参的值复制一份,传递给形参,函数内部对形参的修改不会影响到外部。安全,但是有一次值拷贝,效率低,适合基本数据类型和小型对象。
引用传递:函数行参是实参的别名,传递的是变量本身,函数内部对形参的修改会影响到外部。没有拷贝效率高,适用于需要在函数内部修改外部变量的场景。
指针传递:传递实参的地址,函数通过指针访问或修改原始数据。实参能够被修改。可以传递空指针,需要注意检查。适合数组等需要动态管理内存的场景。
引用传递较为简单,适合简单场景下使用提高效率;指针传递更灵活,更适合底层操作。
new/delete 和 malloc/free 的差别
:
栈和堆的区别
内存对齐为什么重要
提高访问效率:大多数现代CPU在访问对齐的数据时速度更快。例如,访问对齐的4字节整型数据只需要一次总线操作,而非对齐则可能需要多次操作。对齐的数据能更好地利用CPU缓存和内存总线。
跨平台兼容:保证代码在不同硬件和平台下的正确性与安全性。
支持高性能计算:高性能硬件和指令集通常要求数据严格对齐,否则无法利用硬件加速。
多态的实现原理
多态分为:
静态多态(编译时多态):如函数重载、模板、运算符重载等,编译期决定调用关系。
动态多态(运行时多态):如虚函数、接口继承等,运行期决定调用关系。
主要讲解运行时多态。运行时多态依赖于虚函数表(vtable)和虚指针(vptr)。
1 虚函数表(vtable)
当类中声明了虚函数时,编译器为该类生成一个虚函数表。虚函数表是一个存储虚函数地址的数组,每个虚函数对应一个入口。
2 虚指针(vptr)
每个包含虚函数的对象内部都会有一个隐藏的虚指针(vptr),指向该对象所属类的虚函数表。
3 运行时绑定
当通过基类指针或引用调用虚函数时,程序会根据对象的虚指针找到虚函数表,从而调用实际的函数实现。
拷贝构造函数和移动构造函数什么时候触发
拷贝构造函数触发时机:
1 用一个对象初始化另一个同类型对象:MyClass b = a;
2 对象作为值参数传递给函数:func(a);
3 函数返回值为对象类型,即按值返回;
MyClass create() {
MyClass temp;
return temp; // 可能会调用拷贝构造(在C++11后,这里会被优化成移动构造)
}
MyClass b = create();4 显式调用拷贝构造函数:MyClass b(a);
移动构造函数触发时机:
1 用右值对象初始化另一个对象:MyClass b = std::move(a);
2 对象作为右值参数传递给函数:func(std::move(a));
3 函数返回值为对象类型;
MyClass create() {
MyClass temp;
return temp; // 可能会调用拷贝构造(在C++11后,这里会被优化成移动构造)
}
MyClass b = create();4 显式调用移动构造函数:MyClass b(std::move(a));
RAII的思想和理解
RAII(Resource Acquisition Is Initialization,资源获取即初始化)是C++中重要的一种编程思想,实现自动管理资源。
核心思想是 “资源的生命周期绑定到对象的生命周期”,具体而言就是:
在对象创建(初始化)时获取资源(如内存、文件句柄、锁等);在对象销毁(析构)时自动释放资源。
这样可以确保资源的正确释放,避免资源泄露或重复释放等问题。
RAII通常通过构造函数和析构函数来实现:
构造函数负责获取和初始化资源;析构函数负责释放资源。
我们常见的智能指针(std::unique_ptr、std::shared_ptr)、锁管理(std::lock_guard、std::unique_lock)等都是遵循了RAII思想,自动管理资源,提高了代码的安全性、健壮性和可维护性。
vector 扩容机制
std::vector内部维护一个连续的动态数组,指针指向堆内存。涉及三个核心成员:起始指针、当前元素个数(size)、当前分配容量(capacity)。
vector 扩容只在 push_back / insert / resize 等导致 size==capacity 时触发:
重新分配更大的内存,通常是当前容量的2倍;
将原有元素拷贝或移动(如果支持则优先移动构造)到新数组;
释放旧的空间;
更新指针和容量信息;
扩容后旧迭代器、指针、引用全部失效。
如何避免频繁扩容?
• reserve(n) 事先分配;
• 在已知元素数量时,用 vector<int> v; v.reserve(1000); 可一次性搞定。
模板如何在编译时期展开的
C++模板(template)是在编译时期展开的,这一过程成为模板的实例化(template instantiation)。当用具体类型调用模板时,编译器会为每种类型生成对应的代码。
语法分析阶段:编译器识别到模板的定义;
实例化阶段:遇到模板被具体类型调用时,编译器用该类型替换模板参数,生成具体的代码,即“展开”;
类型检查:编译器会检查实例化后的代码是否有语法和类型错误。
在编译时期展开,能保证类型安全,并且没有运行时开销。
C++ 11/14常见的新特性有哪些
C++11新特性:
1 自动类型推断(auto)
auto x = 10; // x 自动推断为 int2 基于范围的 for 循环
std::vector<int> v(1, 2, 3);
for (auto& elem : v) {
std::cout << elem << std::endl;
}3 右值引用和移动语义
void func(std::vector<int>&& v); // 右值引用4 智能指针(std::unique_ptr,std::share_ptr,std::weak_ptr)
std::unique_ptr<int> ptr(new int(5));5 lambda表达式
auto add = [](int a, int b) { return a+ b; };6 nullptr关键字
int* p nullptr; // 替代NULL7 constexpr常量表达式
// 如果x是编译时常量,则square(x)也是编译时常量
constexpr int square(int x) { return x * x; }8 std::thread多线程库
std::thread t([](){ std::cout << "Hello\n"; });
t.join();9 模板别名
template<typename T>
using Vec = std::vector<T>;C++ 14新特性:
1 范型 lambda 捕获(lambda表达式可以auto推断参数类型)
auto add = [](auto a, auto b) { return a + b; };2 变量模板
template<typename T>
constexpr T pi = T(3.1415926);3 返回值类型推断(函数返回值类型可自动推断)
auto func() { return 123; } // 返回值自动推断为 intmutex、spinlock、condition_variable的区别
mutex是一种线程同步机制,用于保证同一时间只有一个线程能访问某个资源。当一个线程获得了mutex,其他线程必须等待,直到该线程释放mutex。等待期间,线程会被操作系统挂起,不再占用CPU。这种机制适合临界区较长、资源竞争激烈的场景。
spinlock(自旋锁)也是一种互斥锁,但它的等待方式不同。没有获得锁的线程不会被挂起,而是会不断地循环检查锁是否可用(“自旋”),直到获得锁。这样会持续占用CPU,适合临界区很短、锁持有时间很短的场景。自旋锁不适合长时间等待,否则会浪费大量的CPU资源。通常用原子操作(如std::atomic_flag)实现。
condition_variable(条件变量)用于线程之间的通信和同步。它允许线程在某个条件不满足时主动挂起等待,直到其他线程通知(notify_one()或notify_all())条件已经满足。常见于生产者-消费者模型或事件通知机制。等待期间,线程会被操作系统挂起,不占用CPU。条件变量通常需要搭配mutex使用来保护条件的检查和修改。
inline、宏、constexpr的区别
inline(内联函数):
inline用于修饰函数,建议编译器将函数代码在调用处展开,以减少函数调用的开销。
主要用于短小、频繁调用的函数;
是否真的内联由编译器决定,inline只是建议;
有类型检查,能安全地处理参数和返回值。
宏定义:
宏是预处理器提供的文本替换机制,用 #define 定义。
在编译之期,宏会被原样替换为定义的内容;
没有类型检查,容易出错,比如运算符优先级问题;
常用于常量、简单的代码片段,但现代C++推荐使用constexpr或inline。
constexpr(常量表达式):
constexpr用于声明常量或函数,要求其值能在编译期间确定。
用于定义编译期间的常量,提高效率;
可以用于变量、函数、类成员等;
有类型检查,语法安全,推荐使用。
评论区