类的成员变量如何进行初始化
(1)构造函数初始化列表
class MyClass {
private:
int a;
double b;
public:
MyClass(int x, double y) : a(x), b(y) {} // 初始化列表
};
(2)构造函数体内赋值
class MyClass {
private:
int a;
double b;
public:
MyClass(int x, double y) {
a = x; // 先调用默认构造,再赋值
b = y;
}
};
2.什么是重载?什么是重写?
重载是对同一个名字的函数实现不同的功能(具有不同的参数列表),重写是对继承基类中的虚函数进行重新定义。
3.C++如何处理异常?
通过try catch来捕获异常,通过throw来显示地抛出异常。
4.C++中什么是命名空间?为什么使用命名空间?
命名空间用来组织代码,避免名字冲突。不同命名空间相同标识符不会冲突。
using namespace MyNamespace; myFunction(); // 直接调用
5.const成员函数不会修改任何成员变量,只能在const对象上调用const成员函数
class MyClass {
public:
void display() const {
// 不能修改任何成员变量
}
};
const MyClass obj;
obj.display(); // 合法
6.如何实现多继承?
通过多个基类之间添加逗号实现
class Base1 {
public:
void display() { /*...*/ }
};
class Base2 {
public:
void show() { /*...*/ }
};
class Derived : public Base1, public Base2 {
public:
void func() {
display(); // 调用Base1的方法
show(); // 调用Base2的方法
}
};
7.为什么list不能使用sort
因为list不是连续的内存空间,如果使用std:sort则需要是连续的内存空间
对于list可以使用list的对象.sort
#include <list>
#include <algorithm>
std::list<int> myList = {4, 2, 3, 1};
myList.sort(); // 对列表进行排序
vector是连续的内存
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> vec = {3, 1, 4, 1, 5, 9};
std::sort(vec.begin(), vec.end()); // std::sort 可以用于 std::vector
for (int v : vec) {
std::cout << v << " ";
}
return 0;
}
8.C++的容器适配器有哪些?
1.stack:先进后出结构,只能在栈顶插入和删除元素
底层容器为 deque(双端队列)或vector
using namespace std;
int main(){
stack<int> s;
s.push(1);
s.push(2);
s.push(3);
while (!s.empty()){
cout<<s.top();
s.pop();
}
}
# 3 2 1
2.queue:先进先出结构,只能在队尾插入元素,在队头删除元素
底层容器为deque
#include <iostream>
#include <queue>
using namespace std;
int main(){
queue <int> p;
p.push(1);
p.push(2);
p.push(3);
while (!p.empty()){
cout << p.front() << " ";
p.pop();
}
return 0;
}
# 1 2 3
3.priority_queue:一个特殊的队列,每个元素都有一个优先级,优先级高的元素会排在队列前面,默认情况下会将最大的元素放在队首。
底层容器为vector
#include <iostream>
#include <queue>
using namespace std;
int main(){
priority_queue<int> pq;
pq.push(1);
pq.push(2);
pq.push(3);
while (!pq.empty()){
cout << pq.top();
pq.pop();
}
}
9.STL中各个容器的实现原理?
1.vector
定义:动态数组的封装,使用连续内存来存储元素。
时间复杂度:
(1)随机访问时间复杂度为O(1)
(2)在末尾进行插入和删除的时间复杂度为O(1),在中间或开头的时间复杂度为O(n)
当需要扩展时,会分配更大的内存(通常是现在内存的两倍),并且将元素拷贝到新内存中。
2.deque(双端队列)
定义:通常由多个小块(缓冲区)的连续内存组成
特点:
(1)支持在两端进行高效的插入和删除操作,时间复杂度为O(1)。
(2)随机访问的时间复杂度为O(1),略低于vector,因为元素存储在多个缓冲区内。
(3)内存管理比较复杂,需要维护多个缓冲区。
3.list
定义:双向链表的实现,每个节点包含指向前后节点的指针
特点:
(1)支持高效的插入和删除操作,时间复杂度为O(1)
(2)随机访问时间复杂度为O(n),因为必须遍历链表。
(3)内存使用效率低,每个节点都需要存储额外的指针。
10.解释一下类的静态成员和静态函数
静态成员和静态函数都是属于类的,而不是属于类的实例的,所有类的实例都共享相同的静态成员。
(1)静态成员
定义:静态成员在内存中只分配一次
静态成员可以通过类名或者对象来访问
class MyClass {
public:
static int staticVar; // 声明静态成员
};
// 在类外定义静态成员
int MyClass::staticVar = 0;
MyClass::staticVar = 5; // 通过类名访问
MyClass obj;
obj.staticVar = 10; // 也可以通过对象访问
(2)静态函数
只能访问静态成员,因为没有对象上下文
class MyClass {
public:
static void staticFunction() {
// 静态函数实现
}
};
MyClass::staticFunction(); // 通过类名调用静态函数
什么时候使用static?为什么使用static?
需要数据对象为整个类服务而非某个对象服务,同时又力求不破坏类的封装性。
为了实现类似于全局变量的功能,同时实现了只有继承的类可以访问。
11.什么是移动语义?
移动语义允许一个资源从一个对象移动到另一个对象上,而不需要进行深拷贝,可以显著提高性能,减少内存开销,这对处理大型数据结构时非常有效。
class MyClass {
public:
MyClass(MyClass&& other) noexcept {
// 转移资源
data = other.data;
other.data = nullptr; // 将原对象的资源置空
}
};
12. 什么是智能指针?
用于自动管理动态分配内存,避免内存泄露和指针悬挂的问题
std::unique_ptr
:用于独占管理对象,确保对象在唯一拥有者超出作用域时自动释放。std::shared_ptr
:用于共享管理对象,多个指针可以共同拥有同一个对象,引用计数机制管理内存。std::weak_ptr
:用于持有对shared_ptr
的非拥有性引用,防止循环引用和内存泄漏。
(1)unique_ptr
每个unique_ptr只能指向一个对象,不能被复制;支持移动语义,可以通过移动构造函数和移动运算符转移所有权。
#include <memory>
void example() {
std::unique_ptr<int> ptr1(new int(10)); // 创建 unique_ptr
// std::unique_ptr<int> ptr2 = ptr1; // 编译错误,不能复制
std::unique_ptr<int> ptr2 = std::move(ptr1); // 转移所有权
} // ptr2 超出作用域时自动释放内存
(2)shared_ptr
多个shared_ptr可以指向同一个对象,引用计数管理对象的生命周期;当最后一个shared_ptr被销毁或重置时,所指向的对象会被自动释放。
#include <memory>
void example() {
std::shared_ptr<int> ptr1(new int(10)); // 创建 shared_ptr
std::shared_ptr<int> ptr2 = ptr1; // 共享所有权
} // ptr1 和 ptr2 超出作用域时,内存会自动释放
(3)weak_ptr
与shared_ptr配合使用,提供对对象的非拥有性引用,不影响计数;防止循环引用,通常用于打破shared_ptr的循环引用问题。
#include <memory>
void example() {
std::shared_ptr<int> ptr1(new int(10));
std::weak_ptr<int> weakPtr = ptr1; // 创建 weak_ptr
if (auto sharedPtr = weakPtr.lock()) { // 检查是否有效
// 使用 sharedPtr
} // weakPtr 不会增加引用计数
}
13.什么是RAII机制?
RAII基本思想是将资源的生命周期与对象的生命周期绑定在一起,从确保资源的自动管理
在构造对象时获取所需的资源,在析构函数中释放资源。
14.什么是模板类?
模板类和模板函数是通用的泛型编码机制,可以定义通用的数据结构和通用的算法
template <typename T>
T add(T a, T b) {
return a + b;
}
int main() {
int intResult = add(3, 4); // 使用 int 类型
double doubleResult = add(3.5, 2.5); // 使用 double 类型
return 0;
}
template <typename T>
class Box {
public:
Box(T value) : value(value) {}
T getValue() const { return value; }
private:
T value;
};
int main() {
Box<int> intBox(10); // 使用 int 类型
Box<double> doubleBox(3.14); // 使用 double 类型
std::cout << intBox.getValue() << std::endl; // 输出 10
std::cout << doubleBox.getValue() << std::endl; // 输出 3.14
return 0;
}
15.如何实现线程和并发操作?
(1)使用thread来创建和管理线程
(2)使用mutex来进行互斥锁的管理,对共享资源进行同步,防止数据竞争。
(3)使用条件变量用于线程间的协调,与互斥锁一起使用
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void producer() {
{
std::lock_guard<std::mutex> lock(mtx);
ready = true; // 设置条件
}
cv.notify_one(); // 通知等待的线程
}
void consumer() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return ready; }); // 等待条件
std::cout << "Consumer consumed!" << std::endl;
}
int main() {
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
16.C++的虚拟继承
虚拟继承是为了解决菱形继承问题
A
/ \
B C
\ /
D
如果不使用虚拟继承,在D中调用基类的函数的时候,会先通过B调用A中的函数,再通过C调用A中的函数。
如果在B,C中使用了虚拟继承,那么只会调用一次A
#include <iostream>
class Base {
public:
Base() {
std::cout << "Base Constructor" << std::endl;
}
void show() {
std::cout << "Base Show" << std::endl;
}
};
class Derived1 : virtual public Base {
public:
Derived1() {
std::cout << "Derived1 Constructor" << std::endl;
}
};
class Derived2 : virtual public Base {
public:
Derived2() {
std::cout << "Derived2 Constructor" << std::endl;
}
};
class Final : public Derived1, public Derived2 {
public:
Final() {
std::cout << "Final Constructor" << std::endl;
}
};
int main() {
Final obj; // 创建 Final 类的对象
obj.show(); // 调用 Base 的方法
return 0;
}
17.什么是函数对象?
18.什么是lambda表达式?
Lambda 表达式的基本语法如下:
[capture](parameters) -> return_type { body }
- capture:捕获外部变量,可以通过值或引用来捕获。
- parameters:参数列表,类似于普通函数的参数。
- return_type:返回类型,可以省略,C++ 会自动推导。
- body:函数体,包含要执行的代码。
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
// 使用 lambda 表达式打印每个元素
std::for_each(numbers.begin(), numbers.end(), [](int n) {
std::cout << n << " ";
});
std::cout << std::endl;
// 使用 lambda 表达式计算总和
int sum = 0;
std::for_each(numbers.begin(), numbers.end(), [&sum](int n) {
sum += n;
});
std::cout << "Sum: " << sum << std::endl;
return 0;
}
19.堆(heap)和栈(stack)的区别?
栈:自动分配和释放内存;用于存储局部变量和函数调用信息,内存管理由编译器控制。访问速度快,因为内存管理简单且靠近CPU寄存器。
堆:由程序员手动分配和释放内存。访问速度慢,内存管理复杂且需要动态查找空闲内存。
20.左值和右值的区别?