嵌入式笔试准备

文件组合

将传输文件切分成多个部分,按照原排列顺序,每部分文件编号为一个正整数。

class Solution {
public:
    vector<vector<int>> fileCombination(int target) {
        vector<vector<int>> res;
        int sum = 0;
        for(int i=1; i<target;i++){
            sum = i;
            for(int j=i+1; j<target; j++){
                sum += j;
                if(sum == target){
                    vector<int> temp;
                    for(int k=i; k<=j; k++){
                        temp.push_back(k);
                    }
                    res.push_back(temp);
                }else if(sum > target){
                    break;
                }
            }
        }
        return res;
    }
};

早餐组合

在这里插入图片描述

在这里插入图片描述

求2~2000的素数个数,时间复杂度优于O(nlogn)

int countPrimes(int n){
	if(n < 2){
		return 0;
	}
	vector<bool> isPrime(n+1, true);
	isPrime[0] = isPrime[1] = false;
	for(int i=2; i*i <= n; i++){
		if(isPrime[i]){
			for(int j=i*i; j<=n; j+=i){
				isPrime[j] = false;
			}
		}
	}
	
	int primeCount = 0;
	for(int i=2; i<=n; i++){
		if(isPrime[i]){
			++count;
		}
	}
	return primeCount;
}

isPrime数组:用一个布尔类型的数组isPrime来标记每个数是否是素数,初始化为true。

  1. 从2开始遍历到sqrt(n),因为一个合数的最小质数一定小于等于它的平方根。
  2. 如果i是素数,那么i的倍数一定不是素数,因此将i的倍数都标记为false。
  3. 这里优化的地方在于,对于一个合数j,它会被它的最小质因数筛掉,而不会被大的质因数重复筛选掉,避免了重复计算。

常用的IO多路复用方式

IO多路复用是一种高效的I/O处理机制,它允许单个进程同时监控多个文件描述符(如网络套接字),一旦某个描述符就绪(可读或可写),就能及时通知程序进行相应的读写操作。
大大提高了程序处理并发连接的能力,广泛应用于高性能网络服务器。

为什么需要IO多路复用:

  • 传统阻塞I/O的弊端:每个连接都需要一个线程来处理,当连接数量增多时,线程开销过大,系统消耗资源严重。
  • IO多路复用的优势:单个线程可以监控多个连接,有效地利用系统资源,提高并发性能。

select
将所有文件描述符放到一个数组中,轮训遍历这个数组,检查每个文件描述符的状态。
缺点:

  • 单个进程能监视的文件描述符数量存在最大限制(通常为1024)。
  • 需要频繁切换用户态和内核态,效率较低。
  • 需要每次把整个描述符集合从用户态拷贝到内核态,开销较大。

poll
与select类似,也是通过轮询的方式检查文件描述符的状态。

  • 使用链表来存储文件描述符,解决了描述符数量的限制。
  • 其它方面与select类似,仍然存在效率问题。

epoll
epoll使用事件驱动的方式,只有当某个文件描述符的状态发生变化时,才会通知应用程序。

  • 效率更高:epoll使用红黑树和回调机制,避免了每次都线性遍历全部连接。
  • 支持大量文件描述符:理论上支持百万级别。
  • 内核事件表:内核中维护一个事件表,将用户注册的事件添加到这个表中。

select

select是最早的一种I/O多路复用机制,在POSIX标准中被广泛支持。

select允许进程监视多个文件描述符,一旦这些描述符中的任何一个变为可读、可写或者有异常发生,select就会返回。

缺点是select的性能随着监控的文件描述符数量增加而下降,因为每次调用都需要对所有描述符进行遍历。

select 是最早的 IO 多路复用机制,出现在 UNIX 系统中。它允许程序监视多个文件描述符,以查看是否可以进行 IO 操作(如读、写或有异常发生)。

select接受三个文件描述符集合(可读、可写、异常),以及一个超时时间。
程序将这些集合传递给select,select监听这些描述符,一旦其中的某个或多个描述符就绪,select 就会返回,程序再对就绪的描述符进行相应的 IO 操作。

兼容性好,几乎在所有的 UNIX 系统上都可以使用。
使用简单,易于理解和实现。

POLL

poll与select类似,但没有文件描述符数量的限制,并且使用了更灵活的数据结构(链表),可以监视更多的文件描述符。

但是它仍然需要遍历所有的文件描述符,因此在处理大量描述符时,性能没有本质提升。

EPOLL

epoll是Lnux下专门为解决select和poll的性能问题而设计的,它们使用了事件通知机制,而不是遍历文件描述符。

epoll有两种工作模式:水平触发和边缘触发,后者能够高效地处理大量并发连接。

epoll非常适用于高并发服务器的开发。

首先通过epoll_create创建一个epoll实例,然后通过epoll_ctrl将需要监控的文件描述符添加到epoll实例中,最后调用epoll_wait等待就绪的事件。

Linux常用的信号

Linux系统中,信号是一种进程间通信机制,用于异步通知进程某个事件的发生。
当一个进程接收到信号时,它可以采取相应的动作,如终止、忽略或者执行特定的处理函数。

信号的作用:

  • 进程控制:终止进程、暂停进程、继续进程等。
  • 异常处理:处理程序错误、硬件故障等异常情况。
  • 进程间通信:在进程之间传递信息。

常用信号:

  • SIGINT:中断信号,通常由Ctrl+C产生,用于终止前台进程。
  • SIGKILL:终止进程信号,无法被忽略或捕获。
  • SIGALRAM:定时器信号,用于实现定时功能。
  • SIGCHLD:子进程状态改变信号,当子进程终止或停止时,父进程会收到该信号。
  • SIGHUP:终端挂起或控制进程终止。通常用于通知进程重新读取配置文件。
  • SIGINT:中断信号,由Ctrl+C发送,用于终止前台进程。
  • SIGQUIT:由Ctrl+\发送,通常用于生成核心转储并终止进程。
  • SIGUSR1、SIGUSR2:由用户定义的信号,可以由用户和程序自行使用。

进程可以通过以下几种方式处理信号:

  • 忽略信号:不做任何处理。
  • 捕获信号:定义信号处理函数,当接收到信号时,执行该函数。
  • 默认处理:系统默认的处理方式,通常是终止进程。

使用kill命令/函数向指定进程发送信号,硬件中断可以产生信号。

Linux系统中的七种主要文件类型

  1. 普通文件(-):最常见的文件类型,用于存储各种类型的数据,包括文本、图形、音频、视频等。
  2. 目录文件(d):用于组织其它文件和目录,形成树状的文件系统结构。/etc、/home、/usr
  3. 链接文件(l)又称符号链接,指向另一个文件或目录的指针,分为软链接和硬链接。软链接相当于Windows的快捷方式,指向文件的路径。源文件删除后,软链接失效。硬链接:指向文件的inode节点,多个硬链接指向同一个inode。ln -s /etc/passwd mypasswd创建一个指向/etc/passwd的软链接
  4. 块设备文件(b)用于访问块设备,如硬盘、U盘等。块设备以固定大小的块为单位进行数据传输。如/dev/sda、/dev/sdb。
  5. 字符设备文件(c)用于访问字符设备,如键盘、鼠标、串口等,字符设备以字节为单位进行数据传输。/dev/tty
  6. 管道文件(p):用于进程间通信,提供一个单向的数据流。mkfifo mypipe创建一个名为mypipe的管道文件。
  7. 套接字文件(s):用于网络通信,实现进程间通过网络进行数据交换。/tmp/mysocket。

使用ls -l查看文件类型,输出的第一个字符就是。
file

/usr目录

在Linux系统中,/usr目录是一个非常重要的目录,是Unix System Resource的缩写。

  • /usr/bin:存放用户和系统管理员使用的大多数可执行文件,这些文件并不是系统启动时必须的,而是日常操作和应用软件需要用到的命令。
  • /usr/sbin:存放系统管理员使用的管理命令,这些命令通常用于系统维护和管理。

#include <head.h> 和 #include “head.h” 的区别

#include 指令用于包含头文件,将头文件的内容复制到当前源文件中。

#include <head.h>

  • 编译器会优先在系统指定的标准头文件目录中查找名为head.h的文件。这些目录通常包含标准库的头文件,如stdio.h,stdlib.h等。

#include “head.h”

  • 编译器会首先在当前源文件所在的目录中查找名为head.h的文件,如果找不到,再按系统指定的规则到其它目录中查找。

Linux线程间的同步与互斥方法

在Linux系统中,多线程编程非常常见,但多个线程同时访问共享资源时,很容易引发数据竞争问题。为了确保程序的正确性和稳定性,必须采用合适的同步与互斥机制。

  • 同步:指多个线程按照一定的顺序执行,确保某些特定的处理顺序。
  • 互斥:指在某一特定时间段内只允许一个线程访问共享资源。

互斥锁
一次只能被一个线程持有。

信号量
信号量是一个计数器,可以实现多个线程对多个共享资源的访问。

条件变量
条件变量与互斥锁配合使用,允许线程等待某个特定的条件的发生。实现线程间的同步,一个线程可以等待另一个线程通知它某个条件已经满足。

读写锁
读写锁允许多个线程同时读取共享资源,但同一个时间只允许一个线程写入。

自旋锁
自旋锁是一种忙等待的锁,当一个线程试图获取已经持有的锁时,会一直忙等待,直到释放。

互斥锁:适合保护单个共享资源,确保互斥访问。
信号量:适合控制多个线程对有限数量资源的访问。
条件变量:适合实现线程间的同步,等待特定条件。
读写锁:适合读操作远多于写操作的场景。
自旋锁:适合锁持有时间较短的场景,但过度使用可能导致CPU空转。

线程拥有自己的堆栈和局部变量

排序算法中哪些是不稳定的

不稳定的排序算法是指排序过程中,无法保证相同值的元素的相对顺序不会改变。

  • 快速排序
  • 选择排序
  • 希尔排序
    将序列分为若干子序列,分别进行直接插入排序,待整个序列中的记录基本有序时,再对全体记录进行一次直接插入排序。
  • 堆排序
    在堆调整的过程中,可能会破坏相等元素的相对顺序。

对于大量数据的传输,共享内存通常是进程间同步的最佳选择。

共享内存直接映射到进程的地址空间,省去了内核态和用户态的切换,数据传输效率极高。
共享内存可以支持非常大的数据传输,不受限制。
进程可以直接对共享内存进行读写操作,就像访问自己的内存一样。

  1. 一个进程创建一个共享内存段,并指定其大小。
  2. 其它进程通过共享内存段的标识符获取到该共享内存段,并将其映射到自己的地址空间。
  3. 各个进程可以直接对映射的共享内存进行读写操作,实现数据交换,
  4. 为了保证多个进程对共享内存的访问是同步的,通常会结合信号量等同步机制来实现。

管道:单向数据流,适合用于父子进程间的通信。

用C++写一个单例类

编写一个单例类(Singleton)在C++中非常常见,单例模式确保一个类中只有一个实例,并提供一个全局访问点。

class Sigleton{
private:
	//私有构造函数,防止直接实例化
	Singleton(){
		std::cout << "Singleton istance created." << std::endl;
	}

	//防止拷贝构造和赋值操作
	Singleton(const Sigleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;

public:
	//获取单例实例的静态方法
	static Sigleton& getInstance(){
		static Singleton istance;
		return instance;
	}

	void doSomething(){
		std::cout << "Doing something with the Singleton instance." << std::endl;
	}
};

int main(){
	// 获取单例实例,并调用其方法
    Singleton& instance = Singleton::getInstance();
    instance.doSomething();

    // 再次获取单例实例,验证是否是同一个对象
    Singleton& anotherInstance = Singleton::getInstance();
    anotherInstance.doSomething();
}

C++中重载与覆盖的区别

重载
在同一个作用域内,多个函数具有相同的名字,但是参数列表不同(参数的个数、类型或顺序不同)。
通过不同的参数列表来实现功能的重用。
编译器根据函数调用时的实参列表来选择合适的函数。

覆盖
在继承体系中,子类重新定义了基类中的虚函数,函数名、参数列表和返回值类型都相同。

实现多态性,根据对象的实际类型来调用不同的函数。
基类中的函数必须声明为虚函数(virtual),子类中重写该函数。

  • 作用域:重载在相同作用域。覆盖在不同作用域(基类和子类)。
  • 函数名:都相同。
  • 参数列表:重载不同,覆盖相同。
  • 返回值:重载可以不同,覆盖相同。
  • 覆盖实现多态性。
  • 重载是编译时确定,覆盖是运行时确定。

全局变量和局部变量在内存中的区别

存储位置

  • 全局变量:通常存储在全局数据区(静态存储区)。这区域在程序编译时就分配了内存,直到程序结束后才释放。
  • 局部变量:存储在栈区。当函数被调用时,栈会为局部变量分配内存;当函数执行完毕后,这些内存会释放。

生命周期

  • 全局变量:从程序运行开始到程序结束,整个生命周期都存在于内存中。
  • 局部变量:只在函数或代码块执行时存在。当函数执行结束或代码块退出后,局部变量的内存会释放。
  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

饼干饼干圆又圆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值