来自:阿Q技术站


一面

1、C++结构体和类的区别,类默认的访问权限,结构体可以定义成员函数吗?


1. 默认访问权限


  • 结构体(struct):成员变量和成员函数默认是 public 访问权限。
  • 类(class):成员变量和成员函数默认是 private 访问权限。


struct MyStruct {

    int x;          // 默认是 public

    void foo() {}   // 默认是 public

};


class MyClass {

    int y;          // 默认是 private

    void bar() {}   // 默认是 private

};


2. 语法上和使用上的区别


  • 定义方式:虽然 struct 和 class 可以用来定义数据成员和成员函数,但使用 class 更常见于表示具有行为和状态的对象,而 struct 更常用于表示纯粹的数据结构。
  • 继承:在继承时,class 的继承默认是 private,而 struct 的继承默认是 public。


struct Base1 {};

class Base2 {};


// 继承默认权限不同

struct Derived1 : Base1 {}; // public 继承

class Derived2 : Base2 {};  // private 继承


3. 结构体是否可以定义成员函数 


C++ 的 struct 可以定义成员函数。实际上,struct 和 class 除了默认访问权限不同外,语法上几乎是一样的。


struct MyStruct {

    int x;

    void setX(int val) {

        x = val;

    }

    int getX() {

        return x;

    }

};


2、多态的意义?

多态指的是同一个接口可以有不同的实现方式。多态通过允许不同类型的对象以相同的方式进行处理,极大地提高了代码的灵活性和可扩展性。多态主要通过继承和接口实现,并且可以分为编译时多态和运行时多态。


多态的意义

1. 提高代码重用性和可维护性: 多态允许你编写更加通用的代码。例如,可以编写一个函数来处理不同类型的对象,而不需要了解这些对象的具体类型。这样,当需要增加新的类型时,只需要新增类的实现,而不需要修改已经存在的代码。


// 基类

class Animal {

public:

    virtual void makeSound() = 0; // 纯虚函数

};


// 派生类

class Dog : public Animal {

public:

    void makeSound() override {

        cout << "Woof" << endl;

    }

};


class Cat : public Animal {

public:

    void makeSound() override {

        cout << "Meow" << endl;

    }

};


void makeAnimalSound(Animal* animal) {

    animal->makeSound();

}


int main() {

    Dog dog;

    Cat cat;

    makeAnimalSound(&dog); // 输出 Woof

    makeAnimalSound(&cat); // 输出 Meow

}


2. 简化代码: 多态可以用统一的接口来操作不同类型的对象,简化了代码的复杂度和可读性。例如,在上面的代码中,无论是 Dog 还是 Cat,你都可以通过调用 makeSound 来发出声音,而不需要分别为每种类型写不同的处理逻辑。


3. 增强系统的扩展性: 多态使得系统更容易扩展。当需要添加新的功能时,只需要新增实现类,而不需要修改现有的代码。例如,如果需要新增一个 Bird 类,只需要继承 Animal 并实现 makeSound 方法。


4. 实现动态绑定: 通过多态,程序在运行时可以根据对象的实际类型进行方法调用,而不是在编译时确定调用哪个方法。这种动态绑定使得程序更加灵活和动态。


多态的实现方式

  1. 继承:通过继承基类的接口并重写其方法,子类可以表现出不同的行为。
  2. 虚函数:在基类中定义虚函数(virtual functions),并在子类中重写这些虚函数。通过基类指针或引用调用虚函数时,会调用实际对象的实现。
  3. 接口:在某些语言(如 Java 和 C#)中,通过实现接口(interface)来实现多态。接口定义了方法的签名,具体的实现由类提供。


3、重载和重写的区别?


重载(Overloading)

重载是指在同一个类中定义多个同名方法,但这些方法具有不同的参数列表(参数类型或参数个数)。编译器根据方法的参数列表来区分这些方法。在C++中,构造函数也可以被重载。


特点

  1. 方法名相同:重载的方法必须具有相同的名称。
  2. 参数列表不同:重载的方法必须具有不同的参数列表(参数类型或参数个数)。
  3. 返回类型可以不同:虽然返回类型可以不同,但返回类型不是区分重载方法的依据。
  4. 同一个类中:重载的方法必须定义在同一个类中。

例子


class Print {

public:

    void display(int i) {

        cout << "整数: " << i << endl;

    }

    void display(double f) {

        cout << "浮点数: " << f << endl;

    }

    void display(string s) {

        cout << "字符串: " << s << endl;

    }

};


int main() {

    Print obj;

    obj.display(5);      // 调用 display(int)

    obj.display(3.14);   // 调用 display(double)

    obj.display("Hello");// 调用 display(string)

    return 0;

}


重写(Overriding)

重写是指子类重新定义从基类继承的方法,目的是提供子类自己的实现版本。重写的方法必须具有相同的名称、参数列表和返回类型。重写通常与多态(Polymorphism)结合使用。


特点

  1. 方法名相同:重写的方法必须具有与基类方法相同的名称。
  2. 参数列表相同:重写的方法必须具有与基类方法相同的参数列表。
  3. 返回类型相同:重写的方法必须具有与基类方法相同的返回类型。
  4. 基类和子类之间:重写的方法在基类中声明,在子类中实现。
  5. 虚函数:在C++中,基类方法通常需要声明为虚函数(virtual)以允许子类重写。


例子


class Animal {

public:

    virtual void makeSound() {

        cout << "Animal sound" << endl;

    }

};


class Dog : public Animal {

public:

    void makeSound() override { // 重写基类的 makeSound 方法

        cout << "Woof" << endl;

    }

};


int main() {

    Animal* animal = new Dog();

    animal->makeSound(); // 调用的是 Dog 类的 makeSound 方法

    delete animal;

    return 0;

}


顺便吆喝一下,技术大厂,前后端测试,来看看


4、TCP/IP 三次握手的过程,为什么要3次?

在建立连接之前,Client处于CLOSED状态,而Server处于LISTEN的状态。


1. 第一次握手(SYN-1):

客户端发送一个带有 SYN 标志的 TCP 报文段给服务器,表示客户端请求建立连接。

客户端选择一个初始序列号(ISN)并将其放入报文段中,进入 SYN_SENT 状态。


2. 第二次握手(SYN + ACK):

服务器收到客户端发送的 SYN 报文段后,如果同意建立连接,会发送一个带有 SYN 和 ACK 标志的报文段给客户端,表示服务器接受了客户端的请求,并带上自己的 ISN。

服务器进入 SYN_RCVD 状态。


3. 第三次握手(ACK):

客户端收到服务器发送的 SYN+ACK 报文段后,会发送一个带有 ACK 标志的报文段给服务器,表示客户端确认了服务器的响应。

客户端和服务器都进入 ESTABLISHED 状态,连接建立成功,可以开始进行数据传输。


为什么需要三次握手?


1. 确保双方都能发送和接收数据:

第一次握手确认客户端的发送能力和服务器的接收能力。

第二次握手确认服务器的发送能力和客户端的接收能力。

第三次握手确认客户端的发送能力和服务器的接收能力。


2. 防止旧的连接请求误导双方:

通过三次握手,双方都能确认对方的状态是最新的,有效避免了网络中的旧的、延迟的SYN包造成的错误连接。


3. 防止重复数据包干扰:

三次握手确保双方都能有效处理重复的数据包,并建立一个唯一的连接。


5、进程和线程的区别?


定义


进程(Process):

进程是操作系统分配资源的基本单位。每个进程有自己独立的内存空间,包括代码段、数据段、堆、和栈。

进程之间相互独立,进程的创建、执行和销毁都是由操作系统管理的。


线程(Thread):

线程是进程的一个执行单元,是CPU调度的基本单位。一个进程可以包含多个线程,这些线程共享进程的资源(如内存和文件描述符)。

线程之间的切换比进程切换更轻量,因为线程共享进程的资源,不需要频繁的资源分配和回收。


区别


1. 内存和资源:

进程:每个进程都有自己独立的地址空间和资源,相互之间不会直接干涉。进程之间的通信需要通过进程间通信(IPC)机制,如管道、消息队列、共享内存等。

线程:线程共享同一个进程的地址空间和资源,所以线程之间的通信更加直接、快速,但也带来了一些同步和互斥的问题。


2.创建和销毁:

进程:创建和销毁进程的开销较大,因为操作系统需要为进程分配和回收大量的资源。

线程:创建和销毁线程的开销较小,因为线程共享进程的资源,不需要重新分配内存。


3. 切换开销:

进程:进程切换需要上下文切换,包括保存和恢复寄存器、内存映射等,开销较大。

线程:线程切换开销较小,因为线程共享同一进程的上下文,只需要保存和恢复少量的状态信息。


4. 独立性和安全性:

进程:由于进程独立,进程间的错误不会相互影响,一个进程崩溃不会影响其他进程。

线程:由于线程共享资源,一个线程的错误(如内存泄漏、死锁)可能影响整个进程中的其他线程。


使用场景

进程:适用于需要高独立性和安全性的任务,比如不同用户的程序、服务和应用之间的隔离。

线程:适用于需要高效并发的任务,如多线程服务器、并行计算等,需要在同一进程内执行多个任务。