C++基础

类的成员变量如何进行初始化

(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.左值和右值的区别?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值