中高级软件工程师的c语言面试题

文章目录

      • 问题1:解释 `volatile` 关键字的作用及其应用场景。
      • 问题2:解释C语言中的内存对齐(Memory Alignment)以及为什么需要对齐。
      • 问题3:解释C语言中的“严格别名规则”(Strict Aliasing Rule),以及如何避免相关问题。
      • 问题4:解释并实现C语言中的浮点数比较。
      • 问题5:实现一个线程安全的单例模式(Singleton)在C中。
      • 问题6:解释C语言中的“堆栈溢出”(Stack Overflow)以及如何防止它。
      • 问题7:解释 "memcpy", "strcpy", "strncpy", 和 "memmove",并实现 "strncpy" 和 "memmove"。
      • 问题8:实现 memcompare 和 strcompare。
      • 问题9:实现一个生产者-消费者模型,使用互斥锁和条件变量。
      • 问题10:解释并实现快速排序算法(Quick Sort)。
      • 问题11:实现一个LRU缓存机制。
      • 问题12:解释C语言中的指针数组和数组指针的区别。
      • 问题13:解释C语言中的函数指针及其用途,并写一个例子来说明。
      • 问题14:解释C语言中的指针算术(Pointer Arithmetic)及其应用。
      • 问题15:解释C语言中的变长数组(Variable Length Array, VLA),并实现一个示例。
      • 问题16:解释并实现一个简单的内存池(Memory Pool)。
      • 问题17:解释C语言中的预处理器指令和宏,并讨论它们的高级用法和潜在的陷阱。
      • 问题18:解释C语言中的 `static` 关键字在函数内部和外部的不同作用。
      • 问题19:解释C语言中的联合体(Union)及其应用场景。
      • 问题20:解释C语言中的位域(Bit Fields),并实现一个示例。

问题1:解释 volatile 关键字的作用及其应用场景。

答案:
volatile 关键字告诉编译器,该变量可能会被程序外部(如硬件或其他线程)修改,因此编译器不应对该变量进行优化,应该每次都从内存中读取变量的值,而不是使用寄存器中的缓存值。

应用场景:

  • 硬件寄存器: 对于硬件设备的寄存器映射,使用 volatile 确保每次读取寄存器时获取的是最新的值。
  • 多线程编程: 在多线程环境中,使用 volatile 变量可以防止编译器对这些变量进行优化,确保线程间的可见性。
  • 信号处理函数: 在信号处理函数中使用的变量应该声明为 volatile,以防止编译器优化导致的问题。

问题2:解释C语言中的内存对齐(Memory Alignment)以及为什么需要对齐。

答案:
内存对齐是指数据在内存中的存储地址按照一定的规则进行排列,以提高内存访问的效率。大多数现代计算机体系结构要求数据以其大小的倍数对齐(如4字节的整数要以4字节对齐)。

需要对齐的原因:

  • 性能原因: 许多处理器在内存对齐时可以更快地读取和写入数据。如果数据未对齐,处理器可能需要进行两次内存访问,影响性能。
  • 硬件限制: 有些硬件体系结构不支持非对齐的内存访问,会导致程序崩溃或产生错误。

内存对齐示例:

#include <stdio.h>

struct Example {
    char a;    // 1字节
    int b;     // 4字节
    short c;   // 2字节
};

int main() {
    struct Example e;
    printf("Size of Example: %lu\n", sizeof(e));
    printf("Offset of a: %lu\n", offsetof(struct Example, a));
    printf("Offset of b: %lu\n", offsetof(struct Example, b));
    printf("Offset of c: %lu\n", offsetof(struct Example, c));
    return 0;
}

问题3:解释C语言中的“严格别名规则”(Strict Aliasing Rule),以及如何避免相关问题。

答案:
严格别名规则指定了不同类型的指针不能互相转换访问,否则行为是未定义的。这是编译器优化的基础之一,违反这一规则可能导致不可预期的结果。

避免问题的方法:

  • 使用 char* 类型进行字节级别的内存操作,因为它被视为可以指向任何类型的数据。
  • 避免类型转换,尽量使用相同类型的指针进行操作。
  • 如果必须进行类型转换,可以使用 union,但要注意这种方式并不是在所有情况下都是安全的。

示例:

union Data {
    int i;
    float f;
};

int main() {
    union Data data;
    data.i = 42;
    printf("As int: %d\n", data.i);
    printf("As float: %f\n", data.f);
    return 0;
}

问题4:解释并实现C语言中的浮点数比较。

答案:
在C语言中,直接比较两个浮点数是否相等是不可靠的,因为浮点数在表示小数时存在精度问题。因此,通常通过判断两个浮点数之差的绝对值是否小于一个很小的阈值(epsilon)来确定它们是否接近相等。

实现代码:

#include <stdio.h>
#include <math.h>

#define EPSILON 1e-6

int areAlmostEqual(float a, float b, float epsilon) {
    return fabs(a - b) < epsilon;
}

int main() {
    float num1 = 0.1f + 0.2f;
    float num2 = 0.3f;

    if (areAlmostEqual(num1, num2, EPSILON)) {
        printf("num1 and num2 are approximately equal.\n");
    } else {
        printf("num1 and num2 are not equal.\n");
    }

    return 0;
}

问题5:实现一个线程安全的单例模式(Singleton)在C中。

答案:
实现一个线程安全的单例模式可以使用双重检查锁定(Double-Checked Locking)机制:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

typedef struct {
    int data;
} Singleton;

Singleton *instance = NULL;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

Singleton *getInstance() {
    if (instance == NULL) {
        pthread_mutex_lock(&mutex);
        if (instance == NULL) {
            instance = (Singleton *)malloc(sizeof(Singleton));
            instance->data = 0;
        }
        pthread_mutex_unlock(&mutex);
    }
    return instance;
}

int main() {
    Singleton *s1 = getInstance();
    Singleton *s2 = getInstance();
    printf("s1 data: %d\n", s1->data);
    printf("s2 data: %d\n", s2->data);
    return 0;
}

问题6:解释C语言中的“堆栈溢出”(Stack Overflow)以及如何防止它。

答案:
堆栈溢出发生在程序使用的堆栈空间超过系统为其分配的最大空间时。通常发生在递归调用过深或分配过大局部变量时。

防止方法:

  • 避免过深的递归调用,使用迭代方式替代。
  • 避免在堆栈上分配过大的局部变量,可以使用动态内存分配(如 malloc)。
  • 增加系统堆栈大小限制(具体方法依操作系统而不同)。

问题7:解释 “memcpy”, “strcpy”, “strncpy”, 和 “memmove”,并实现 “strncpy” 和 “memmove”。

解释:

  • memcpy:用于从源地址复制一块内存内容到目标地址。不会处理内存重叠问题,假设源和目标区域是独立的。
  • strcpy:用于将源字符串复制到目标字符串,包括终止的空字符(\0)。不进行边界检查,可能导致缓冲区溢出。
  • strncpy:用于将源字符串最多复制n个字符到目标字符串。如果源字符串长度小于n,目标字符串将用空字符填充。如果源字符串长度大于或等于n,不会自动添加终止的空字符(\0)。
  • memmove:用于从源地址复制一块内存内容到目标地址,处理内存重叠问题,确保数据在重叠情况下也能正确复制。

实现 strncpymemmove

#include <stdio.h>
#include <stdint.h>
#include <stddef.h>

// 实现strncpy
char *my_strncpy(char *dest, const char *src, size_t n) {
    size_t i;

    // 复制源字符串到目标字符串,最多n个字符
    for (i = 0; i < n && src[i] != '\0'; i++) {
        dest[i] = src[i];
    }

    // 如果源字符串长度小于n,用空字符填充
    for ( ; i < n; i++) {
        dest[i] = '\0';
    }

    return dest;
}

// 实现memmove
void *my_memmove(void *dest, const void *src, size_t n) {
    uint8_t *d = (uint8_t *)dest;
    const uint8_t *s = (const uint8_t *)src;

    if (d == s) {
        return dest;
    }

    if (d < s) {
        // 如果目标地址在源地址之前,直接从前往后复制
        for (size_t i = 0; i < n; i++) {
            d[i] = s[i];
        }
    } else {
        // 如果目标地址在源地址之后,从后往前复制,避免重叠问题
        for (size_t i = n; i != 0; i--) {
            d[i - 1] = s[i - 1];
        }
    }

    return dest;
}

int main(void) {
    // 测试my_strncpy
    char dest1[20];
    my_strncpy(dest1, "Hello, World!", 5);
    printf("my_strncpy result: %s\n", dest1);

    // 测试my_memmove
    char dest2[20] = "Goodbye";
    my_memmove(dest2 + 4, dest2, 7);
    printf("my_memmove result:

 %s\n", dest2);

    return 0;
}

问题8:实现 memcompare 和 strcompare。

实现思想:

  • memcompare:逐字节比较两个内存块的内容,直到找到不同的字节或比较完指定的字节数。
  • strcompare:逐字符比较两个字符串的内容,直到找到不同的字符或遇到字符串的结束符('\0')。
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>

// 实现 memcompare
int memcompare(const void *ptr1, const void *ptr2, size_t num) {
    const uint8_t *p1 = (const uint8_t *)ptr1;
    const uint8_t *p2 = (const uint8_t *)ptr2;

    for (size_t i = 0; i < num; i++) {
        if (p1[i] != p2[i]) {
            return p1[i] - p2[i];
        }
    }
    return 0;
}

// 实现 strcompare
int strcompare(const char *str1, const char *str2) {
    while (*str1 != '\0' && *str2 != '\0') {
        if (*str1 != *str2) {
            return (unsigned char)*str1 - (unsigned char)*str2;
        }
        str1++;
        str2++;
    }
    return (unsigned char)*str1 - (unsigned char)*str2;
}

int main(void) {
    // 测试 memcompare
    char mem1[] = {0, 1, 2, 3, 4};
    char mem2[] = {0, 1, 2, 4, 4};
    int result_memcompare = memcompare(mem1, mem2, sizeof(mem1));
    printf("memcompare result: %d\n", result_memcompare);

    // 测试 strcompare
    char str1[] = "Hello";
    char str2[] = "HelLo";
    int result_strcompare = strcompare(str1, str2);
    printf("strcompare result: %d\n", result_strcompare);

    return 0;
}

问题9:实现一个生产者-消费者模型,使用互斥锁和条件变量。

实现思想:

  • 互斥锁 用于保护缓冲区,确保同一时刻只有一个线程(生产者或消费者)访问缓冲区。
  • 条件变量 用于同步生产者和消费者之间的等待和通知。当缓冲区满时,生产者等待 cond_produce 条件变量;当缓冲区为空时,消费者等待 cond_consume 条件变量。
  • 生产者 线程在生产数据时,首先获取互斥锁,如果缓冲区已满,则等待 cond_produce 条件变量,直到有空闲空间。然后将数据放入缓冲区,发信号通知 cond_consume 消费者有新数据可供消费,最后释放互斥锁。
  • 消费者 线程在消费数据时,首先获取互斥锁,如果缓冲区为空,则等待 cond_consume 条件变量,直到有新数据。然后从缓冲区取数据,发信号通知 cond_produce 生产者有新空闲空间,最后释放互斥锁。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <stdint.h>
#include <unistd.h>

#define BUFFER_SIZE 10

static int buffer[BUFFER_SIZE];
static uint32_t count = 0;

static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond_produce = PTHREAD_COND_INITIALIZER;
static pthread_cond_t cond_consume = PTHREAD_COND_INITIALIZER;

static void *producer(void *param) {
    (void)param;  // 避免未使用参数的警告
    int32_t item;
    while (1) {
        item = rand() % 100;
        pthread_mutex_lock(&mutex);
        while (count == BUFFER_SIZE) {
            pthread_cond_wait(&cond_produce, &mutex);
        }
        buffer[count] = item;
        count++;
        printf("Produced: %d\n", item);
        pthread_cond_signal(&cond_consume);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
    return NULL;
}

static void *consumer(void *param) {
    (void)param;  // 避免未使用参数的警告
    int32_t item;
    while (1) {
        pthread_mutex_lock(&mutex);
        while (count == 0) {
            pthread_cond_wait(&cond_consume, &mutex);
        }
        count--;
        item = buffer[count];
        printf("Consumed: %d\n", item);
        pthread_cond_signal(&cond_produce);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
    return NULL;
}

int main(void) {
    pthread_t prod, cons;
    pthread_create(&prod, NULL, producer, NULL);
    pthread_create(&cons, NULL, consumer, NULL);
    pthread_join(prod, NULL);
    pthread_join(cons, NULL);
    return 0;
}

问题10:解释并实现快速排序算法(Quick Sort)。

实现思想:

  • 分治法:快速排序通过递归将数组分成较小的子数组进行排序。
  • 选取基准:选择一个基准元素(通常是数组的最后一个元素)。
  • 分区操作:将小于基准的元素移到基准左侧,大于基准的元素移到基准右侧。具体通过两个指针从数组两端向中间扫描交换元素实现。
  • 递归排序:对基准元素左侧和右侧的子数组递归进行快速排序,直到子数组长度为1。
#include <stdio.h>
#include <stdint.h>

static void swap(int32_t *a, int32_t *b) {
    int32_t temp = *a;
    *a = *b;
    *b = temp;
}

static int32_t partition(int32_t arr[], int32_t low, int32_t high) {
    int32_t pivot = arr[high];
    int32_t i = low - 1;
    for (int32_t j = low; j < high; j++) {
        if (arr[j] < pivot) {
            i++;
            swap(&arr[i], &arr[j]);
        }
    }
    swap(&arr[i + 1], &arr[high]);
    return i + 1;
}

static void quickSort(int32_t arr[], int32_t low, int32_t high) {
    if (low < high) {
        int32_t pi = partition(arr, low, high);
        quickSort(arr, low, pi - 1);
        quickSort(arr, pi + 1, high);
    }
}

static void printArray(int32_t arr[], int32_t size) {
    for (int32_t i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main(void) {
    int32_t arr[] = {10, 7, 8, 9, 1, 5};
    int32_t n = sizeof(arr) / sizeof(arr[0]);
    quickSort(arr, 0, n - 1);
    printf("Sorted array: \n");
    printArray(arr, n);
    return 0;
}

问题11:实现一个LRU缓存机制。

实现思想:

  • 双向链表:用于维护缓存的顺序,最近使用的元素移动到链表头,最久未使用的元素移到链表尾。
  • 哈希表:用于快速查找缓存中的元素,键是缓存的键,值是双向链表中的节点。
  • 缓存操作
    • 查询:从哈希表中查找元素,如果找到,则将该元素移动到链表头。
    • 插入:如果缓存满,则移除链表尾的元素(即最久未使用的元素),然后将新元素插入链表头并更新哈希表。
    • 删除:从链表中移除指定元素,并更新哈希表。
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

typedef struct Node {
    int32_t key;
    int32_t value;
    struct Node *prev;
    struct Node *next;
} Node;

typedef struct {
    int32_t capacity;
    int32_t size;
    Node *head;
    Node *tail;
    Node **hashTable;
} LRUCache;

static Node *createNode(int32_t key, int32_t value) {
    Node *newNode = (Node *)malloc(sizeof(Node));
    if (newNode != NULL) {
        newNode->key = key;
        newNode->value = value;
        newNode->prev = NULL;
        newNode->next = NULL;
    }
    return newNode;
}

static LRUCache *createCache(int32_t capacity) {
    LRUCache *cache = (LRUCache *)malloc(sizeof(LRUCache));
    if (cache != NULL) {
        cache->capacity = capacity;
        cache->size = 0;
        cache->head = create

Node(0, 0);
        cache->tail = createNode(0, 0);
        if (cache->head != NULL && cache->tail != NULL) {
            cache->head->next = cache->tail;
            cache->tail->prev = cache->head;
            cache->hashTable = (Node **)calloc(capacity, sizeof(Node *));
        }
    }
    return cache;
}

static void removeNode(Node *node) {
    if (node != NULL) {
        node->prev->next = node->next;
        node->next->prev = node->prev;
    }
}

static void addToHead(LRUCache *cache, Node *node) {
    if (cache != NULL && node != NULL) {
        node->next = cache->head->next;
        node->prev = cache->head;
        cache->head->next->prev = node;
        cache->head->next = node;
    }
}

static Node *getNode(LRUCache *cache, int32_t key) {
    return cache->hashTable[key % cache->capacity];
}

static void putNode(LRUCache *cache, int32_t key, int32_t value) {
    if (cache != NULL) {
        Node *node = getNode(cache, key);
        if (node != NULL) {
            node->value = value;
            removeNode(node);
            addToHead(cache, node);
        } else {
            Node *newNode = createNode(key, value);
            if (newNode != NULL) {
                if (cache->size == cache->capacity) {
                    Node *tail = cache->tail->prev;
                    removeNode(tail);
                    cache->hashTable[tail->key % cache->capacity] = NULL;
                    free(tail);
                    cache->size--;
                }
                addToHead(cache, newNode);
                cache->hashTable[key % cache->capacity] = newNode;
                cache->size++;
            }
        }
    }
}

static int32_t get(LRUCache *cache, int32_t key) {
    Node *node = getNode(cache, key);
    if (node != NULL) {
        removeNode(node);
        addToHead(cache, node);
        return node->value;
    }
    return -1;  // 如果key不存在,返回-1
}

int main(void) {
    LRUCache *cache = createCache(2);
    putNode(cache, 1, 1);
    putNode(cache, 2, 2);
    printf("Get 1: %d\n", get(cache, 1));  // 返回 1
    putNode(cache, 3, 3);                 // 这个操作会使得key 2作废
    printf("Get 2: %d\n", get(cache, 2));  // 返回 -1 (未找到)
    putNode(cache, 4, 4);                 // 这个操作会使得key 1作废
    printf("Get 1: %d\n", get(cache, 1));  // 返回 -1 (未找到)
    printf("Get 3: %d\n", get(cache, 3));  // 返回 3
    printf("Get 4: %d\n", get(cache, 4));  // 返回 4
    return 0;
}

问题12:解释C语言中的指针数组和数组指针的区别。

答案:

  • 指针数组(Array of Pointers): 一个数组,其中每个元素是一个指针。

    int *arr[10];  // 这是一个包含10个int指针的数组
    
  • 数组指针(Pointer to an Array): 一个指针,指向一个数组。

    int (*p)[10];  // 这是一个指向包含10个int的数组的指针
    

问题13:解释C语言中的函数指针及其用途,并写一个例子来说明。

答案:
函数指针是指向函数的指针变量。它允许程序在运行时动态调用函数。

用途:

  • 回调函数: 在库函数中使用回调函数进行定制操作。
  • 函数表: 实现函数表以简化条件分支操作。

示例:

#include <stdio.h>

void sayHello() {
    printf("Hello, world!\n");
}

int main() {
    void (*funcPtr)() = sayHello;
    funcPtr();  // 调用函数指针执行 sayHello 函数
    return 0;
}

问题14:解释C语言中的指针算术(Pointer Arithmetic)及其应用。

答案:
指针算术允许在指针上进行加减运算。指针加减运算根据指针所指向的类型进行调整。

#include <stdio.h>

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    int *ptr = arr;
    
    printf("Pointer arithmetic:\n");
    for (int i = 0; i < 5; i++) {
        printf("*(ptr + %d) = %d\n", i, *(ptr + i));
    }
    
    printf("Array indexing:\n");
    for (int i = 0; i < 5; i++) {
        printf("arr[%d] = %d\n", i, arr[i]);
    }
    
    return 0;
}

问题15:解释C语言中的变长数组(Variable Length Array, VLA),并实现一个示例。

答案:
变长数组(VLA)是C99标准引入的一种特性,允许数组长度在运行时确定。

#include <stdio.h>

void printArray(int size) {
    int arr[size];
    for (int i = 0; i < size; i++) {
        arr[i] = i * i;
    }
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    int n;
    printf("Enter the size of the array: ");
    scanf("%d", &n);
    printArray(n);
    return 0;
}

问题16:解释并实现一个简单的内存池(Memory Pool)。

答案:
内存池是一种预先分配一大块内存,然后在需要时从这块内存中划分出小块内存,提高内存分配和释放的效率。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#define POOL_SIZE 1024U

typedef struct Block {
    uint32_t size;
    struct Block *next;
} Block;

static Block *freeList = NULL;
static uint8_t memoryPool[POOL_SIZE];

static void initMemoryPool(void) {
    freeList = (Block *)memoryPool;
    freeList->size = POOL_SIZE - sizeof(Block);
    freeList->next = NULL;
}

static void *allocate(uint32_t size) {
    Block *current = freeList;
    Block *previous = NULL;

    while (current != NULL && current->size < size) {
        previous = current;
        current = current->next;
    }

    if (current == NULL) {
        return NULL;  // No suitable block found
    }

    if (current->size > size + sizeof(Block)) {
        Block *newBlock = (Block *)((uint8_t *)current + sizeof(Block) + size);
        newBlock->size = current->size - size - sizeof(Block);
        newBlock->next = current->next;
        current->size = size;
        current->next = newBlock;
    }

    if (previous == NULL) {
        freeList = current->next;
    } else {
        previous->next = current->next;
    }

    return (uint8_t *)current + sizeof(Block);
}

static void freeMemory(void *ptr) {
    if (ptr == NULL) {
        return;
    }

    Block *block = (Block *)((uint8_t *)ptr - sizeof(Block));
    block->next = freeList;
    freeList = block;
}

int main(void) {
    initMemoryPool();

    void *p1 = allocate(100U);
    void *p2 = allocate(200U);

    printf("Allocated memory at %p and %p\n", p1, p2);

    freeMemory(p1);
    freeMemory(p2);

    void *p3 = allocate(150U);
    printf("Allocated memory at %p\n", p3);

    return 0;
}

当然可以,这里是一个关于C语言预处理器指令和宏的复杂面试问题。

问题17:解释C语言中的预处理器指令和宏,并讨论它们的高级用法和潜在的陷阱。

答案:

预处理器指令 是在编译过程中处理的指令,执行一些文本替换、文件包含和条件编译等操作。常见的预处理器指令包括 #define, #include, #if, #ifdef, #ifndef 等。

是通过 #define 定义的预处理器指令,用于定义常量或函数样式的代码替换。

高级用法

  1. 条件编译:根据不同的平台或条件编译不同的代码。

    #ifdef _WIN32
    #define PLATFORM "Windows"
    #elif defined(__linux__)
    #define PLATFORM "Linux"
    #else
    #define PLATFORM "Unknown"
    #endif
    
  2. 宏函数:定义一些常用的代码片段,减少代码重复。

    #define SQUARE(x) ((x) * (x))
    
  3. 文件包含保护:防止头文件被多次包含,使用包含保护。

    #ifndef MYHEADER_H
    #define MYHEADER_H
    // 文件内容
    #endif
    

潜在陷阱

  1. 宏替换陷阱:由于宏替换是简单的文本替换,可能会导致意外的行为。例如:

    #define SQUARE(x) x * x
    int a = SQUARE(1 + 2); // 实际替换为 1 + 2 * 1 + 2
    

    解决方法是使用括号包围宏参数和整个宏定义:

    #define SQUARE(x) ((x) * (x))
    
  2. 多次求值:宏参数在宏替换中可能会被多次求值,导致副作用。

    #define PRINT_AND_INCREMENT(x) printf("%d\n", (x)); (x)++;
    int a = 1;
    PRINT_AND_INCREMENT(a); // 打印1,a变成2
    PRINT_AND_INCREMENT(a++); // 打印2,a变成4,而不是3
    
  3. 调试困难:宏替换发生在编译之前,错误信息可能难以追踪到宏定义,增加调试难度。

  4. 作用域问题:宏没有作用域,可能会影响全局命名空间,导致命名冲突。

问题18:解释C语言中的 static 关键字在函数内部和外部的不同作用。

答案:

  • 在函数内部:

static 变量在函数内部声明时,表示该变量在函数调用之间保持其值不变。函数每次调用时不会重新初始化该变量。

  • 在函数外部: static 变量在函数外部声明时,表示该变量仅在声明它的文件内可见。其他文件不能访问该变量。

示例:

#include <stdio.h>

void func() {
    static int x = 0;  // 保持其值在函数调用之间不变
    x++;
    printf("x = %d\n", x);
}

int main() {
    for (int i = 0; i < 5; i++) {
        func();
    }
    return 0;
}

输出:

x = 1
x = 2
x = 3
x = 4
x = 5

问题19:解释C语言中的联合体(Union)及其应用场景。

答案:
联合体(Union)是一种数据结构,它允许不同的数据类型共用同一段内存。联合体的所有成员共享同一块内存,存储空间大小等于其最大成员的大小。

应用场景:

  • 内存节省: 联合体可以在不同类型的变量之间共享内存,节省内存空间。
  • 数据解析: 联合体常用于解析不同格式的数据,例如网络协议数据包。
#include <stdio.h>

union Data {
    int i;
    float f;
    char str[20];
};

int main() {
    union Data data;
    data.i = 10;
    printf("data.i : %d\n", data.i);
    data.f = 220.5;
    printf("data.f : %f\n", data.f);
    strcpy(data.str, "C Programming");
    printf("data.str : %s\n", data.str);

    return 0;
}

问题20:解释C语言中的位域(Bit Fields),并实现一个示例。

答案:
位域(Bit Fields)是结构体的一部分,允许定义和存储比基本数据类型更小的位段。位域通常用于需要高效存储多个布尔值或小范围整数的情况。

#include <stdio.h>

struct {
    unsigned int age : 3;
} Age;

int main() {
    Age.age = 4;
    printf("Sizeof(Age) : %lu\n", sizeof(Age));
    printf("Age.age : %d\n", Age.age);

    Age.age = 7;
    printf("Age.age : %d\n", Age.age);

    Age.age = 8; // 超出范围,结果是不确定的
    printf("Age.age : %d\n", Age.age);

    return 0;
}

这些问题涵盖了C语言的高级主题,如多线程编程、内存管理、编译器优化规则等

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/715075.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式复古游戏项目开发与实现

大家好,今天看到一个火柴盒项目,非常的小巧,分享给大家,感兴趣的话,可以复刻一个玩一玩。 MicroByte 是一款微型主机,能够运行 NES、GameBoy、GameBoy Color、Game Gear 和 Sega Master 系统的游戏,所有元器件都设计在这 78 x 17 x 40 mm 的封装中。尽管成品尺寸很小,但…

什么是git?

前言 Git 是一款免费、开源的分布式版本控制系统&#xff0c;用于敏捷高效地处理任何或小或大的项目。是的&#xff0c;我对git的介绍就一条&#xff0c;想看简介的可以去百度一下&#x1f618;&#x1f618;&#x1f618; 为什么要用git&#xff1f; OK&#xff0c;想象一下…

【单片机毕业设计选题24008】-基于单片机的寝室系统设计

系统功能: 1. 采用STM32最小系统板控制&#xff0c;将采集到温湿度光照等传感器数据显示在OLED上 2. 通过离线语音模块开关灯&#xff0c;风扇&#xff0c;门。 3. 监测到MQ2烟雾后触发报警。 4. 语音&手动&定时控制窗帘。 5. 按键开启布防模式&#xff0c;布防后…

C语言实现动态栈

#include<stdio.h> #include<stdlib.h> #include<stdbool.h>// 每一个节点的数据类型 typedef struct Node {int data;struct Node * pNext; }NODE, * PNODE; // NODE等价 struct Node PNODE等价于 struct Node *// 栈 typedef struct Stack {PNODE pTop;P…

Modbus为何要转成ProfiNET

Modbus与ProfiNET代表了工业通讯不同阶段的发展&#xff0c;各自具有优缺点。Modbus简单易用&#xff0c;适合小型系统&#xff1b;ProfiNET高效稳定&#xff0c;适用于大型复杂网络。转换Modbus为ProfiNET可提高系统性能和扩展性。实际场景下&#xff0c;升级生产线控制器为Pr…

Golang: 依赖注入与wire —— 构建高效模块化应用的秘诀

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

代码随想录-Day32

122. 买卖股票的最佳时机 II 给你一个整数数组 prices &#xff0c;其中 prices[i] 表示某支股票第 i 天的价格。 在每一天&#xff0c;你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买&#xff0c;然后在 同一天 出售。 返回 你能…

Python文本处理:初探《三国演义》

Python文本处理&#xff1a;初探《三国演义》 三国演义获取文本文本预处理分词与词频统计引入停用词后进行词频统计分析人物出场次数结果可视化完整代码 三国演义 《三国演义》是中国古代四大名著之一&#xff0c;它以东汉末年到晋朝统一之间的历史为背景&#xff0c;讲述了魏…

阿里云服务器-Linux搭建fastDFS文件服务器

阿里云官网购买服务器&#xff0c;一般会有降价活动&#xff0c;这两天就发现有活动&#xff0c;99计划活动&#xff08;在活动期内&#xff0c;续费都是99元&#xff09; 阿里云官网-云服务器ECS 在这里&#xff0c;我购买了这台服务器&#xff0c;活动期内续费每年99元&…

二叉树-距离是K的二叉树节点(hard)

目录 一、问题描述 二、解题思路 1.总体思路&#xff08;DFSBFS结合&#xff09; 2.下面举具体例子来对思路进行解释 (1)返回值在一侧的情况 (2)返回值在两侧的情况 三、代码实现 四、刷题链接 一、问题描述 二、解题思路 1.总体思路&#xff08;DFSBFS结合&#xff0…

对接钉钉Stream模式考勤打卡相关事件的指南

钉钉之前的accessToken是公司级别的&#xff0c;现在的accessToken是基于应用的&#xff0c;接口的权限也是基于应用的。所以第一步是在钉钉开放平台&#xff08;https://open-dev.dingtalk.com/&#xff09;创建一个应用。 创建好应用之后&#xff0c;因为我们后续还需要调用钉…

---异常---

我们在运行程序时总遇到各种与报错&#xff0c;数组越界&#xff0c;空指针的引用&#xff0c;这些在java中都称为异常 对于不同的错误都具有一个与他对应的异常类来秒描述 这是对于数组越界这个类里有的方法&#xff0c;这些是描述异常的 在java中有一个完整的描述异常的类的…

C/C++ Adaline自适应线性神经网络算法详解及源码

个人名片 &#x1f393;作者简介&#xff1a;java领域优质创作者 &#x1f310;个人主页&#xff1a;码农阿豪 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[2435024119qq.com] &#x1f4f1…

MySQL之高级特性(四)

高级特性 查询缓存 什么情况下查询缓存能发挥作用 并不是什么情况下查询缓存都会提高系统性能的。缓存和失效都会带来额外的消耗&#xff0c;所以只有当缓存带来的资源节约大于本身的资源消耗时才会给系统带来性能提升。这跟具体的服务器压力模型有关。理论上&#xff0c;可…

实现贪吃蛇小游戏【简单版】

1. 贪吃蛇游戏设计与分析 1.1 地图 我们最终的贪吃蛇大纲要是这个样子&#xff0c;那我们的地图如何布置呢&#xff1f; 这里不得不讲⼀下控制台窗口的⼀些知识&#xff0c;如果想在控制台的窗口中指定位置输出信息&#xff0c;我们得知道该位置的坐标&#xff0c;所以首先介…

微信小程序毕业设计-博客系统项目开发实战(附源码+论文)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;微信小程序毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计…

龙迅LT9611UXC 2 PORT MIPIDSI/CSI转HDMI 2.1,支持音频IIS/SPDIF输入,支持标准4K60HZ输出

龙迅LT9611UXC描述&#xff1a; LT9611UXC是一个高性能的MIPI DSI/CSI到HDMI2.0转换器。MIPI DSI/CSI输入具有可配置的单端口或双端口&#xff0c;1高速时钟通道和1~4高速数据通道&#xff0c;最大2Gbps/通道&#xff0c;可支持高达16Gbps的总带宽。LT9611UXC支持突发模式DSI视…

Uniapp实现页面滚动Tab吸顶,点击tab内容滚动到对应tab内容位置

思路&#xff1a;运用uniapp原生提供方法uni.createSelectorQuery()获取滚动对应节点的信息&#xff0c;即节点距离页面顶部的距离&#xff0c;再通过uniapp原生监听页面滚动事件onPageScroll&#xff0c;获取页面内容滚动的高度&#xff0c;二者相加即定位到对应节点的滚动距离…

java设计模式和面向对象编程思想

Java设计模式和面向对象编程思想是软件开发中的核心概念&#xff0c;对于构建可维护、可扩展的软件系统至关重要。下面是对这两个主题的知识点总结&#xff1a; 面向对象编程&#xff08;OOP&#xff09;思想 封装&#xff1a;将数据&#xff08;属性&#xff09;和操作这些数据…

如何选择合适的大模型框架:LangChain、LlamaIndex、Haystack 还是 Hugging Face

节前&#xff0c;我们星球组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学。 针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。 合集&#x…