本文发表于入职啦(公众号: ruzhila) 大家可以访问入职啦学习更多的编程实战。
问题描述
身份证号码的验证是一个常见的需求,身份证的号码由17位数字和1位校验码组成,身份证的格式由:
- 6位地址码
 - 8位出生日期码 格式为YYYYMMDD
 - 3位顺序码组成
 - 最后一位是校验码
 
校验码的计算方法如下:
- 将前17位数字的权值分别乘以系数相加
 - 将相加的结果除以11,得到余数
 - 通过余数查找校验码的值
 - 如果校验码的值等于身份证号码的最后一位,则校验通过
 - 如果校验码的值等于10,则校验码为X
 - 如果校验码的值不等于身份证号码的最后一位,则校验不通过
 - 日期的校验,年份的范围是1900-2099年
 - 日期的校验,月份的范围是1-12月
 - 日期的校验,日期的范围是1-31日
 - 日期的校验,闰年的2月份是29天
 - 日期的校验,平年的2月份是28天
 - 日期的校验,4、6、9、11月份是30天
 - 日期的校验,1、3、5、7、8、10、12月份是31天
 - 日期的校验,2月份是28天
 - 日期的校验,闰年的2月份是29天
 - 日期的校验,闰年的判断方法是年份能被4整除,但是不能被100整除,或者能被400整除
 
测试代码
    assertEqual(id_verify("11010519491231002X"), 'X', "1");
    assertEqual(id_verify("110105194912310021"), '\0', "2");
    assertEqual(id_verify("350703198001001237"), '7', "2");解决思路
根据 GB11643-1999中有关公民身份号码的规定,公民身份号码是特征组合码,由十七位数字本体码和一位数字校验码组成。 校验码的计算方法如下:
- 将前17位数字的权值分别乘以系数相加: {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}
 - 将相加的结果除以11,得到余数
 - 通过余数查找校验码的值: {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'}
 - 对日期进行校验,年份的范围是1900-2099年,月份的范围是1-12月,日期的范围是1-31日,闰年的2月份是29天,平年的2月份是28天,4、6、9、11月份是30天,1、3、5、7、8、10、12月份是31天
 
代码实现
身份证号码的校验
如果身份证号码的校验通过,则返回校验码,否则返回'\0',表示校验不通过
char calc_checksum(const char *card) {
    // 加权因子
    int weight[] = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};
    // 校验码对应值
    char checkCodes[] = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};
    int sum = 0;
    if (card == NULL) {
        return '\0';
    }
    for (int i = 0; i < 17; i++) {
        int c = card[i];
        if (c < '0' || c > '9') {
            return '\0';
        }
        sum += (c - '0') * weight[i];
    }
    return checkCodes[sum % 11];
}日期的校验
int isvalid_date(const char *card) {
    const char *date = card + 6;
    int year = 0;
    for (int i = 0; i < 4; i++) {
        year = year * 10 + (date[i] - '0');
    }
    if (year < 1900 || year > 2099) {
        return 0;
    }
    
    int month = 0;
    for (int i = 4; i < 6; i++) {
        month = month * 10 + (date[i] - '0');
    }
    if (month < 1 || month > 12) {
        return 0;
    }
    
    int day = 0;
    for (int i = 6; i < 8; i++) {
        day = day * 10 + (date[i] - '0');
    }
    if (day < 1 || day > 31) {
        return 0;
    }
    
    if (month == 2) {
        if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
            if (day > 29) {
                return 0;
            }
        } else {
            if (day > 28) {
                return 0;
            }
        }
    } else if (month == 4 || month == 6 || month == 9 || month == 11) {
        if (day > 30) {
            return 0;
        }
    }
    return 1;
}完整代码请参考:

总结
日期和校验码分别实现了校验的逻辑,这样当验证码正确的情况下我们再做日期的校验,可以减少不必要的计算。 整个代码没有使用任何的库,只是使用了C语言的基本语法,通过这个练习,我们可以学习如何使用C语言实现一个简单的校验算法。 日期的解析是比较简单的,主要是对年份、月份和日期的校验,通过这个练习,我们可以学习如何解析日期。
所有的后端面试常见的问题,我们每天都会在我们的编程群里面讨论和Code review, 欢迎大家加入我们的编程群,一起学习和进步。

欢迎大家关注 入职啦 (公众号: ruzhila) ,获取更多有趣的编程挑战题和技术干货!







