身份证的奥秘

题目内容:

18位身份证标准在国家质量技术监督局于1999年7月1日实施的 GB11643-1999《公民身份号码》中做了明确的规定。 GB11643-1999《公民身份号码》为GB11643-1989《社会保障号码》的修订版,其中指出将原标准名称"社会保障号码"更名为"公民身份 号码",另外GB11643-1999《公民身份号码》从实施之日起代替GB11643-1989。GB11643-1999《公民身份号码》主要内容如 下:

一、范围

该标准规定了公民身份号码的编码对象、号码的结构和表现形式,使每个编码对象获得一个唯一的、不变的法定号码。

二、编码对象

公民身份号码的编码对象是具有中华人民共和国国籍的公民。

三、号码的结构和表示形式

1、号码的结构

公民身份号码是特征组合码,由十七位数字本体码和一位校验码组成。排列顺序从左至右依次为:六位数字地址码,八位数字出生日期码,三位数字顺序码和一位数字校验码。

2、地址码

表示编码对象常住户口所在县(市、旗、区)的行政区划代码,按GB/T2260的规定执行。

3、出生日期码

表示编码对象出生的年、月、日,按GB/T7408的规定执行,年、月、日代码之间不用分隔符。

4、顺序码

表示在同一地址码所标识的区域范围内,对同年、同月、同日出生的人编定的顺序号,顺序码的奇数分配给男性,偶数分配给女性。

5、校验码

(1)十七位数字本体码加权求和公式

S = Sum(Ai * Wi), i = 0, ... , 16 ,先对前17位数字的权求和 
Ai: 表示第i位置上的身份证号码数字值 
Wi: 表示第i位置上的加权因子 
Wi: 7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2

(2)计算模

Y = mod(S, 11)

(3)通过模得到对应的校验码

Y: 0 1 2 3 4 5 6 7 8 9 10 
校验码: 1 0 X 9 8 7 6 5 4 3 2

四、举例如下:

北京市朝阳区: 11010519491231002X 
广东省汕头市: 440524188001010014

15位的身份证号升级办法:

15位的身份证号:dddddd yymmdd xx p

18位的身份证号:dddddd yyyymmdd xx p y

  • 其中dddddd为地址码(省地县三级)

  • yyyymmdd yymmdd 为出生年月日

  • xx顺号类编码

  • p性别

15 位的 yy 年升为 18 位后,变成 19yy年,但对于百岁以上老人, 则为 18yy 年,此时,他们的最后三位顺序码为996, 997, 998 或 999 来标记。

输入格式:

输入n组身份证号码,第一行为个数,以后每行为身份证号码。

输出格式:

如果输入的身份证号码为15位,则将其升级为18位后显示输出;否则判断其是否为合法身份证号,并逐行输出。

输入样例:

4
350622197904130331
11010519491231002X
110105491231002
110105491231996[回车]

输出样例:

Invalid[回车]
Valid[回车]
11010519491231002X[回车]
110105184912319965[回车]

简要分析:

题目很长,但有用的部分不多,简而言之就是需要输入身份证号,判断位数,如果是15位则补全至18位并输出,如果是18则判断是否合法。

问题分析及解决思路:

流程分析:

0.输入身份证个数及每个身份证

1.计算身份证长度并分15位、18位两类

2.对于15位构造函数补全至18位(两位年份及一位校验码)并输出,注意年份是18还是19开头

3.对于18位构造函数判断其合法性(计算其校验码是否与所给一致)

4.本题最重要的点是考察计算和检验校验码

5.注意字符型数字和整型数字间的关系

细节方案:

0.定义二维字符数组储存,每行长度取到19以保证最后的结束标识符(\0)存在

1.使用数组储存权重、校验码并与其下标建立映射关系辅助计算及检验

2.使用循环,逐个处理身份证号

bug的出现及解决:

一顿分析猛如虎,一看结果,红了一半。这说明思路还有漏洞,究竟在哪里呢?

小插曲:我去闲鱼花了15找bug,大哥没找到bug又给我把钱退回来了hhh

后来我发现,应该是缺少了对输入的身份证位数合法性的判断,即如果输入的值不是15位或18位,则需要输出Invalid。ps:酒吧点炒饭是吧,有意思吗(

完整代码:

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

//定义数组记录权重和校验码
int w[17] = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3 ,7 ,9, 10, 5, 8, 4, 2 };
char y[11] = { '1','0','X','9','8','7','6','5','4','3','2' };

//构造函数判断18位身份证号是否合法
void solve18(char* in)
{
    //定义所需变量
    int i = 0, sum = 0;
    //计算十七位数字本体码加权求和
    for (i = 0; i < 17; i++)
    {
        sum += w[i] * (in[i] - 48);
    }
    //计算模
    int Y = sum % 11;
    //通过模得到对应的校验码并判断其合法性
    if (y[Y] == in[17])//校验码合法
        printf("Valid\n");
    else//校验码不合法
        printf("Invalid\n");
}

//构造函数将15位身份证号转化为18位
void solve15(char* in)
{
    //定义所需变量
    int i = 0;
    //判断是否是19世纪出生,并补足年份位
    if ((in[14] == '6' || in[14] == '7' || in[14] == '8' || in[14] == '9') && in[13] == '9' && in[12] == '9')//是19世纪出生
    {
        //补全出生年份“18”
        for (i = 16; i >= 8; i--)
        {
            in[i] = in[i - 2];
        }
        in[6] = '1';
        in[7] = '8';
    }
    else//非19世纪出生
    {
        //补全出生年份“19”
        for (i = 16; i >= 8; i--)
        {
            in[i] = in[i - 2];
        }
        in[6] = '1';
        in[7] = '9';
    }
    //计算十七位数字本体码加权求和
    int sum = 0;
    for (i = 0; i < 17; i++)
    {
        sum += w[i] * (in[i] - 48);
    }
    //计算模
    int Y = sum % 11;
    //通过模补全对应的校验码
    in[17] = y[Y];
    //输出18位身份证号
    printf("%s\n", in);
}

//主函数部分
int main()
{
    //定义变量储存输入个数
    int n = 0;
    scanf("%d", &n);
    //定义二维字符数组储存身份证号
    char in[64][19] = { '\0' };
    //输入身份证号
    int i = 0;
    for (i = 0; i < n; i++)
        scanf("%s", in[i]);
    //逐个处理身份证号
    for (i = 0; i < n; i++)
    {
        //计算身份证号长度
        int l = strlen(in[i]);
        //按长度分类进行传参
        switch (l)
        {
        case 18:    solve18(in[i]); break;//长度为18位
        case 15:    solve15(in[i]); break;//长度为15位
        default:    printf("Invalid\n"); break;//身份证位数不合法
        }
    }
    return 0;
}

写在最后: 

在找出bug后,我认为测试用例还是仁慈了;按照同样的想法,还可以在身份证的出生年月里埋坑,甚至埋19世纪某闰年2月的坑,这样应该还能再坑死一大批人,但我已经不想再加上这一部分了。

全文仅作为记录,欢迎大佬们的留言。

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

yorunouta

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

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

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

打赏作者

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

抵扣说明:

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

余额充值