C语言如何实现快速排序算法?
-
答案:快排是一种分治算法,选择一个基准元素,将数据划分成两部分,然后递归排序
-
补充:
void quick_sort(int arr[], int start, int end) {//判断是否需要排序if (start >= end){return;} int left = start;int right = end;int pivot = arr[left]; //基准值while (left < right){//实现大于基准值的数字放到左边while ((arr[right] > pivot) && (left < right)){right--;}if (left < right){arr[left] = arr[right]; //将基准值填入left++;}//实现小于基准值的数字放到右while ((arr[left] < pivot) && (left < right)){left++;}if (left < right){arr[right] = arr[left]; //将基准值填入right--;}arr[left] = pivot;}//递归排序quick_sort(arr, start, left - 1);quick_sort(arr, right+1, end); }
C语言中的setvbuf函数作用是什么?
-
答案:setvbuf 用于设置文件流的缓冲区模式和大小,优化文件读写的性能。
-
解析:setvbuf 可以设置缓冲区类型(如全缓冲、无缓冲、行缓冲),并定义缓冲区的大小, 适用于文件流的优化。
-
补充:
缓冲区的类型
缓冲区分为三种类型:全缓冲、行缓冲和不缓冲。
1、全缓冲
在这种情况下,当填满标准I/O缓存后才进行实际I/O操作。全缓冲的典型代表是对磁盘文件的读写。
2、行缓冲
在这种情况下,当在输入和输出中遇到换行符时,执行真正的I/O操作。这时,我们输入的字符先存放在缓冲区,等按下回车键换行时才进行实际的I/O操作。典型代表是键盘输入数据。
3、不缓冲
也就是不进行缓冲,标准出错情况stderr是典型代表,这使得出错信息可以直接尽快地显示出来。
缓冲区的刷新
下列情况会引发缓冲区的刷新:
-
缓冲区满时;
-
执行fflush函数;
-
正常关闭文件。
-
正常退出程序或进程。
-
行缓冲遇到 \n刷新。
如何改变缓存类型
//与缓冲区相关的一些函数 #include <stdio.h> void setbuf(FILE *stream, char *buf); void setbuffer(FILE *stream, char *buf, size_t size); void setlinebuf(FILE *stream); int setvbuf(FILE *stream, char *buf, int mode , size_t size); 参数: stream:这是指向 FILE 对象的指针,该 FILE 对象标识了一个打开的流。 buffer:这是分配给用户的缓冲。如果设置为 NULL,该函数会自动分配一个指定大小的缓冲。 mode: 这指定了文件缓冲的模式:_IOFBF、_IOLBF、_IONBF。 size:这是缓冲的大小,以字节为单位。 返回值: 如果成功,则该函数返回 0,否则返回非零值。
VScode中的stdout属于无缓冲类型
Ubuntu中的stdout属于行缓冲类型(行缓冲遇到换行符会清空缓冲区,而全缓冲不会)
解释C语言中的volatile关键字并给出应用场景。
-
答案:volatile 告诉编译器,该变量的值可能随时被外部因素改变,因此禁止优化该变量的读取和写入。
-
解析:volatile 常用于多线程或硬件寄存器操作等场景,确保每次读取变量时都从内存中获取最新值,避免编译器优化。
-
补充:
-
volatile
的作用-
禁止优化:编译器通常会对变量进行优化,以提高程序的运行效率。例如,如果某个变量在代码中多次赋值,但是值没有改变,编译器可能会认为这个变量不再被使用,从而优化掉一些访问该变量的代码。但是,当变量是
volatile
类型时,编译器不会做这种优化,确保每次访问变量时都进行实际的读取或写入操作。 -
外部改变:
volatile
修饰的变量通常表示该变量的值可以在程序的执行过程中被外部因素(如硬件设备或其他线程)改变。
-
-
常见的应用场景
-
硬件寄存器:在嵌入式系统编程中,硬件设备的寄存器可能会随时被硬件更改。为了保证对这些寄存器的访问不会被优化掉,通常会用
volatile
修饰寄存器变量。 -
多线程编程:在多线程程序中,如果一个线程在修改某个变量,而其他线程正在读取该变量,使用
volatile
修饰该变量可以确保每个线程读取到变量的最新值。
-
如何判断一个链表是否有环?
bool has_cycle(struct ListNode *head) { struct ListNode *slow = head, *fast = head; while (fast && fast->next) { slow = slow->next; fast = fast->next->next; if (slow == fast) return true; } return false; }
如何实现一个 LRU 缓存(Least Recently Used Cache)?
-
答案:使用双向链表和哈希表结合来实现。哈希表提供快速的访问,双向链表维护访问顺序,当缓存满时,删除最久未使用的元素。
-
解析:使用双向链表存储数据,哈希表存储键和节点的映射。每次访问数据时,将对应节点移到链表的头部。若缓存满了,删除链表尾部节点。
如何实现一个线程安全的计数器?
-
答案:使用互斥锁(mutex) 或原子操作来确保多个线程同时访问时不会产生竞态条件。
-
解析:使用pthread mutex_ t来加锁计数器的修改操作,确保线程安全。也可以使用原子操作(如atomic)来避免锁的开销。
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; int counter = 0; void increment() { pthread_mutex_lock(&lock); counter++; pthread_mutex_unlock(&lock); }
-
补充:
std::atomic 是 C++ 提供的一种用于多线程编程的同步原语,保证对数据类型的操作是原子的,即不会被中断或与其他线程产生冲突。它适用于需要在线程之间共享数据而不需要加锁的场景,提供比传统的锁(如 std::mutex)更好的性能。
常用场景 std::atomic 通常用于以下情况:
共享资源的安全访问:在多线程程序中,多个线程需要访问和修改同一变量。 避免锁机制的开销:锁会带来性能上的开销,而 std::atomic 提供了更轻量的原子操作。 简单的同步:当仅需要单个变量的同步时,使用 std::atomic 是非常高效的。
std::atomic 支持的类型 内置基础类型:如 int, bool, char, float, double 等。 自定义类型:可以将自定义结构体/类声明为 std::atomic<T>,但必须保证其满足 "trivially copyable"(平凡可拷贝)要求,即没有自定义构造、析构、拷贝构造和赋值运算符等。
#include <iostream> #include <atomic> #include <thread> std::atomic<int> counter(0); // 初始化原子计数器 void increment() {for (int i = 0; i < 10000; ++i) {counter.fetch_add(1, std::memory_order_relaxed); // 原子增加} } int main() {std::thread t1(increment);std::thread t2(increment); t1.join();t2.join(); std::cout << "Final counter value: " << counter << std::endl; // 输出最终的计数器值 return 0; }