(嵌入式八股)No.1 C语言(2)

2.1 进制基础

常用进制表示

无论是使用哪种语言,使用哪种“进制”,描述的都是同一个“数值”。

int decimal = 100;      // 十进制
int binary = 0b1100100; // 二进制(C99标准支持,前缀0b)
int octal = 0144;       // 八进制(前缀0)
int hex = 0x64;         // 十六进制(前缀0x)


#include <stdio.h>

int main() {
    int num = 100;
    
    printf("十进制: %d\n", num);    // 100
    printf("八进制: %o\n", num);    // 144
    printf("十六进制: %x\n", num);   // 64(小写)
    printf("十六进制: %X\n", num);   // 64(大写)
    printf("带前缀十六进制: %#x\n", num); // 0x64
    
    return 0;
}

进制之间的转换

16进制里,每一位的权重,从右往左数,分别是:16^0, 16^1, 16^2, 16^3, 16^4, ……。

8进制里,每一位的权重,从右往左数,分别是:8^0, 8^1, 8^2, 8^3, 8^4, ……。

2进制里,每一位的权重,从右往左数,分别是:2^0, 2^1, 2^2, 2^3, 2^4, ……。

各进制对照表

2.2 命名规则与语法

标识符命名规则

  • 允许字符:字母(A-Z, a-z)、数字(0-9)、下划线(_)
  • 开头字符:必须是字母或下划线(不能是数字)
  • 大小写敏感:count 与 Count 是不同的变量
  • 长度限制:C89标准至少31个字符有效,C99至少63个字符
  • 关键字不能作为标识符(如int, if, for等)

命名规范示例

// 良好命名示例
int student_count;      // 小写+下划线(snake_case)
float averageScore;     // 驼峰命名法
#define MAX_SIZE 100    // 宏定义全大写
const float PI = 3.14;  // 常量可首字母大写

// 非法命名示例
int 2nd_value;     // ❌ 以数字开头
float total-score; // ❌ 包含减号
char int;          // ❌ 使用关键字

常见命名约定

// 变量:描述性名词
int age, counter, buffer_size;

// 函数:动词开头
void calculate_total();
int get_max_value();

// 指针:p开头或ptr结尾
int *p_data;
int *data_ptr;

// 布尔类型:is/has/can开头
int is_valid;
int has_finished;

操作符优先级

2.3 库函数(如果你的技术栈是 c 语言的话,面试官可能会考察一些库函数的底层实现)

sizeof 和 strlen(这个在笔试里面会考)

sizeofstrlen 是两个非常常用的运算符和函数,它们在功能和用途上有明显的区别。

区别

示例

//普通变量
int a = 10;
printf("%zu\n", sizeof(a));   // 输出:4(假设 int 为4字节)
// strlen(a); ❌ 错误:a不是字符串

//字符串常量 vs 字符数组
char str1[] = "Hello";
char *str2 = "Hello";

printf("%zu\n", sizeof(str1)); // 6 (包括 '\0')
printf("%zu\n", sizeof(str2)); // 8 (指针本身的大小,在64位系统上)
printf("%zu\n", strlen(str1)); // 5 (不含 '\0')
printf("%zu\n", strlen(str2)); // 5
解释:
sizeof(str1):数组占 6 个字节('H','e','l','l','o','\0')
sizeof(str2):只返回指针大小(通常 8 字节)
strlen(str1) / strlen(str2):计算字符串内容长度(直到 \0)

//字符串中途有 \0
char msg[] = "Hi\0NEU";

printf("%zu\n", sizeof(msg)); // 7 (包含整个数组长度)
printf("%zu\n", strlen(msg)); // 2 (到第一个 '\0' 为止)

strlen() 会在遇到第一个 \0 时停止计数,而 sizeof 不会。

总结

  • sizeof: 用于获取变量或数据类型的大小(以字节为单位)。在编译时计算,不会增加运行时的开销。适用于数组、结构体、变量等。
  • strlen: 用于计算字符串的长度(以字符为单位)。在运行时计算,会逐个字符检查字符串,直到遇到终止符 \0。适用于字符串操作,但需要注意字符串的初始化和合法性。

malloc 和 calloc(这个也会考)

malloccalloc 是两个用于动态内存分配的标准库函数。它们都属于 <stdlib.h> 头文件,用于在运行时分配内存。

二者对比

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

int main(void)
{
    int *p1 = (int *)malloc(5 * sizeof(int));
    int *p2 = (int *)calloc(5, sizeof(int));

    for (int i = 0; i < 5; i++)
        printf("malloc[%d] = %d, calloc[%d] = %d\n", i, p1[i], i, p2[i]);

    free(p1);
    free(p2);
    return 0;
}
输出:
malloc[0] = -858993460, calloc[0] = 0
说明 malloc 分配的内存未初始化,而 calloc 分配的内存全是 0。

注意事项:

  • 返回类型是void *,所以在 C 中不需要强制类型转换(但 C++ 需要)。
int *p = malloc(10 * sizeof(int));  // C语言推荐写法
  • 失败返回 NULL,要检查分配结果:
if (p == NULL) { perror("malloc failed"); exit(1); }
  • 必须使用 free() 释放,否则会造成内存泄漏。
  • 使用sizeof确保类型安全:
int *p = malloc(5 * sizeof(*p));  // 推荐写法,更安全
  • calloc清零不仅仅是数值型,结构体、数组、指针都会初始化为 0(指针为 NULL)。
  • 不要混用分配和释放函数:

   用 malloc/calloc/realloc → 用 free。

用 new → 用 delete。

延伸:malloc 底层原理(了解)(来源小林 coding

strcpy 和 memcpy

strcpy用法

#include <stdio.h>
#include <string.h>

int main() {
    char src[] = "Hello";
    char dest[10];

    strcpy(dest, src);
    printf("dest = %s\n", dest);  // 输出: Hello
    return 0;
}
会自动复制 'H' 'e' 'l' 'l' 'o' '\0'。
目标空间必须足够大(至少比源字符串多 1 字节)。

memcpy 用法

#include <stdio.h>
#include <string.h>

int main() {
    int src[3] = {1, 2, 3};
    int dest[3];
    
    memcpy(dest, src, sizeof(src));
    
    for (int i = 0; i < 3; i++)
        printf("%d ", dest[i]);  // 输出: 1 2 3
    return 0;
}
按字节拷贝,共 sizeof(src) 字节;
不需要也不会添加 \0。

场景

适合的函数

字符串复制(以 \0 结尾)

strcpy

任意内存块(包括二进制、结构体、数组)

memcpy

可能存在内存重叠

🚫 不用 memcpy,要用 memmove

2.4 其余常考手撕实现:

你在手写代码时,请自查以下几点:

  1. 判空了没? (输入指针是否为 NULL) —— 不做必挂。
  2. const加了没? (源数据 src 应该是只读的) —— 加了加分。
  3. 返回值对不对? (strcpy 和 memcpy 需要返回 dest 以支持链式操作 len = strlen(strcpy(d, s)) ) —— 体现专业度。
  4. 指针类型转换? (memcpy 操作 void* 必须转为 char*) —— 不做编译报错。
  5. 结束符检查? (字符串循环条件是 \0)。

建议: 重点练习 memmovestrcpy还有memcpy,这是出现频率最高的“杀手锏”。

memcpy (内存拷贝)

考点:void* 指针的转换、按字节拷贝、返回 dest 指针以支持链式调用。

注意: 标准 memcpy不保证处理内存重叠,这是它和 memmove 的区别。

void *my_memcpy(void *dest, const void *src, size_t n) {
    if (dest == NULL || src == NULL) {
        return NULL;
    }
    
    char *p_dest = (char *)dest;
    const char *p_src = (const char *)src;
    
    // 必须转为 char* (按字节) 进行拷贝
    while (n--) {
        *p_dest++ = *p_src++;
    }
    
    return dest;
}

memmove (内存移动) —— 顶级考点

考点:destsrc 的内存区域发生重叠时,如何安全拷贝?

  • 情况 1 (dest < src):从前往后拷(同 memcpy)。
  • 情况 2 (dest > src):从后往前拷(防止源数据被覆盖)。
void *my_memmove(void *dest, const void *src, size_t n) {
    if (dest == NULL || src == NULL) {
        return NULL;
    }

    char *p_dest = (char *)dest;
    const char *p_src = (const char *)src;

    // 判断是否重叠且 dest 在 src 后面
    if (p_dest > p_src && p_dest < p_src + n) {
        // 从后往前拷贝
        p_dest += n - 1;
        p_src += n - 1;
        while (n--) {
            *p_dest-- = *p_src--;
        }
    } else {
        // 正常情况:从前往后拷贝
        while (n--) {
            *p_dest++ = *p_src++;
        }
    }
    
    return dest;
}

strcpy (字符串拷贝)

考点:const 修饰源字符串、判断 \0 结束符、返回 dest

陷阱: 不要忘了最后的 \0 是随循环赋值过去的,还是需要额外补。下面的写法最简洁。

char *my_strcpy(char *dest, const char *src) {
    if (dest == NULL || src == NULL) {
        return NULL;
    }
    
    char *ret = dest; // 保存首地址用于返回
    
    // *dest++ = *src++ 先赋值,再判断值是否为 '\0'
    while ((*dest++ = *src++) != '\0'); 
    
    return ret;
}

strcmp (字符串比较)

考点: 必须使用 unsigned char 进行比较(标准规定),否则处理扩展 ASCII 码时可能出错。

int my_strcmp(const char *s1, const char *s2) {
    assert(s1 != NULL && s2 != NULL); // 面试时最好写上断言或判空

    while (*s1 && (*s1 == *s2)) {
        s1++;
        s2++;
    }
    
    // 转为无符号字符相减
    return *(unsigned char *)s1 - *(unsigned char *)s2;
}

strlen (字符串长度)--中微半导体考了个这个。。。。

考点: 很简单,但要快。不要把 \0 算进去。

size_t my_strlen(const char *str) {
    assert(str != NULL);
    
    const char *p = str;
    while (*p != '\0') {
        p++;
    }
    
    return p - str; // 指针相减得到长度
}

atoi (字符串转整型)

考点: 空格处理、正负号处理、非数字字符处理。

进阶考点: 数据溢出处理(一般面试写出基础版即可,但要口头提到溢出问题)。

int my_atoi(const char *str) {
    if (str == NULL) return 0;

    int result = 0;
    int sign = 1;

    // 1. 跳过前导空格
    while (*str == ' ') str++;

    // 2. 处理正负号
    if (*str == '-' || *str == '+') {
        if (*str == '-') sign = -1;
        str++;
    }

    // 3. 处理数字
    while (*str >= '0' && *str <= '9') {
        result = result * 10 + (*str - '0');
        
        // 此处可以加入溢出判断逻辑
        // if (result > INT_MAX ...) 
        
        str++;
    }

    return sign * result;
}
#嵌入式软件八股##牛客在线求职答疑中心##开工第一帖#
泻湖花园嵌入式Offer指南 文章被收录于专栏

从入门到上岸,一站式搞定求职! 本硕纯机械,无竞赛无论文,后转行嵌入式软件开发(因为课题组师哥转嵌入式拿到30Woffer之后狠狠心动),秋招最终收获35W+offer可以为27届或者28届的的UU们提供参考,可以关注一下!!!

全部评论
大家可以关注一下,近期会陆续更新
1 回复 分享
发布于 今天 16:23 山东

相关推荐

最喜欢秋天的火龙果很...:第一份工作一定要往大的去,工资低点没事。后面换工作会更好找,即使你去小公司,你也不可能不会换工作的。所以找大的去
点赞 评论 收藏
分享
哈哈哈,你是老六:百度去年裁员分评不好,赶紧弄点红包
点赞 评论 收藏
分享
不会做题的小熊:我感觉我就算是找不到工作,我也不会作弊进去,作弊进去感觉一方面是自己不踏实,其次就是都靠作弊了,那后面肯定工作的心态是不一样的,没有一种内驱力。
点赞 评论 收藏
分享
评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务