【华为od刷题(C++)】HJ18 识别有效的IP地址和掩码并进行分类统计

发布于:2025-07-04 ⋅ 阅读:(21) ⋅ 点赞:(0)

我的代码:

#include<iostream>// 这个头文件包含了用于输入和输出的标准库
#include<cstring>// 这个头文件提供了与C风格字符串(字符数组)相关的操作函数
#include<vector>
// vector 可以存储任意类型的数据,并且支持高效的随机访问、动态增长、删除等操作
#include<cctype>
// 这个头文件包含了一些字符处理的函数,这些函数可以用于检查或转换单个字符的状态
#include<string.h>
// 这个头文件是C标准库中的一部分,提供了与C风格字符串(字符数组)相关的函数
using namespace std;

int Acount = 0;// 计数A类地址
int Bcount = 0;// 计数B类地址
int Ccount = 0;// 计数C类地址
int Dcount = 0;// 计数D类地址
int Ecount = 0;// 计数E类地址
int errorCount = 0;// 计数错误的IP或掩码
int privateCount = 0;// 计数私有IP地址

vector<string>ip;// 存储IP地址的每个部分
vector<string>mask;// 存储掩码地址的每个部分

//判断字符串是否合法
/*用于判断单个字符串是否是合法的IP段(0到255之间的数字)
如果字符串为空或不是数字,或者转换后的整数不在合法范围内,则返回false*/
bool isValidStr(string str) {
    if (str == "") return false;//检查传入的字符串是否为空
    //如果 str 是一个空字符串(""),则返回 false,表示这个字符串无效

    if (stoi(str) < 0 || stoi(str) > 255) return false;
    //这行代码将字符串 str 转换为整数并检查它是否在 0 到 255 之间
    //如果转换后的整数小于 0 或大于 255,返回 false,表示该字符串无效

    /*stoi 是 C++ 中的字符串到整数的转换函数,
    会尝试将字符串 str 转换成一个整数

    注意,如果 str 不是一个有效的数字字符串,
    stoi 会抛出异常(std::invalid_argument 或 std::out_of_range),
    这部分并没有在代码中处理,因此可能会导致程序崩溃或行为不稳定*/

    return true;
    /* 如果前面的条件都不满足,
    即字符串非空,且转换后的整数在有效范围内(0 到 255 之间),
    则返回 true,表示该字符串是有效的 */
}

//判断IP地址是否合法
/*用来判断给定的IP地址是否合法
它会遍历每一个IP段,利用isValidStr函数来判断每一个段是否符合要求
只有当所有段都合法时,才返回true*/
bool isValidIp(vector<string>& ip) {
    /*vector<string>& ip 是一个引用参数,
    允许函数直接操作传入的 vector<string> 类型的容器,
    而不是创建一个副本*/

    for (auto i : ip) {
        //这是一个 for 循环,用来遍历 ip 中的每一个元素
        //这里使用了 auto 关键字,自动推导出 i 的类型为 string

        if (isValidStr(i) == false) return false;
        //对于 ip 中的每一个部分,调用 isValidStr(i) 判断它是否有效
        //如果某个部分无效,立即返回 false,表示整个 IP 地址无效
    }
    return true;
    //如果 ip 中的所有部分都有效,最终返回 true,表示该 IP 地址合法
}


//判断掩码是否合法
/*用来判断子网掩码是否合法
合法的子网掩码应满足以下条件:
各个部分的值不能随意组合,必须是 255、254、252、248、240、224、128 或 0
子网掩码的值从左到右不能升高,即 255 > 254 > 252 > ... > 0
第一个段不能是0,最后一个段不能是255
如果这些条件都满足,则返回true,否则返回false*/
bool isValidMask(vector<string>&mask) {
   int mmask[4];// 定义一个数组,用于存储掩码的4个部分(通常是四个字节)
    int k=0;
    for(auto s:mask){
        /* mask 是一个 vector<string> 类型的输入参数,包含4个字符串,
        每个字符串代表一个掩码部分(类似 "255", "255", "255", "0")*/

        if(s=="") return false;
        // 如果某个掩码部分为空,返回false,表示非法

        mmask[k]=stoi(s);// 将字符串转换为整数,并存储在mmask数组中
        //cout<<mmask[k];
        k++;// 递增k,处理下一个掩码部分

    }
    if(mmask[0]==0||mmask[3]==255) return false;
    //如果 mmask 数组的第一个元素为 0 或第四个元素为 255,那么返回 false
    for(int i=1;i<4;i++){
        if(mmask[i]>mmask[i-1]) return false;
    }
    /*掩码的各部分应该是严格递减的
    也就是说,mmask[0] >= mmask[1] >= mmask[2] >= mmask[3]
    这是因为掩码的作用是逐步掩盖IP地址的主机部分,而不是随机分配,
    所以掩码部分应该从左到右逐渐减小
    如果任何部分大于前一个部分,说明掩码不符合规则,返回 false*/

    int truenum=0;
    for(int i=0;i<4;i++){
        if(mmask[i]==255 || mmask[i] == 254 || mmask[i] == 252 || mmask[i] == 248 || mmask[i] == 240 || mmask[i] == 224 || mmask[i]== 191 || mmask[i] == 128 || mmask[i]==0) truenum++;
    }
    /*这里检查掩码的每一部分是否合法
    合法的掩码部分应该是特定的数值,例如 255, 254, 252, 248 等,
    这些是常见的子网掩码部分(这些数值表示不同的网络和主机部分的位数)*/

    if(truenum==4) return true;
    return false;
    //truenum 计数器用于记录掩码中出现合法值的次数
    //如果掩码的每个部分都符合这些合法值之一,truenum 会等于 4
}

//给合法的IP地址分类
/*用来根据IP地址的第一段(即 ip[0])来判断该IP属于哪一类:
A类(1.0.0.0到126.255.255.255)
B类(128.0.0.0到191.255.255.255)
C类(192.0.0.0到223.255.255.255)
D类(224.0.0.0到239.255.255.255)
E类(240.0.0.0到255.255.255.255)
如果是私有IP地址,还会增加privateCount计数*/
void ipClass(vector<string>& ip) {
    int num = stoi(ip[0]);// 将IP地址的第一个部分(网络段)转换为整数

    if (num >= 1 && num <= 126) {
        // A 类 IP 地址的范围是 1.0.0.0 到 126.0.0.0
        Acount++;// 如果 num 在这个范围内,Acount++ 统计 A 类 IP 地址数量
        if (stoi(ip[0]) == 10 && stoi(ip[1]) >= 0 && stoi(ip[1]) <= 255) privateCount++;
        /* 如果该 IP 地址是 10.x.x.x(即私有 IP 地址),
        则 privateCount++ 统计私有 IP 地址数量 */
    } else if (num >= 128 && num <= 191) {
        //B 类 IP 地址的范围是 128.0.0.0 到 191.255.255.255
        Bcount++;//如果 num 在这个范围内,Bcount++ 统计 B 类 IP 地址数量
        if (stoi(ip[0]) == 172 && stoi(ip[1]) >= 16 &&
                stoi(ip[1]) <= 31) privateCount++;
    /*如果该 IP 地址是 172.16.0.0 到 172.31.255.255 之间(即私有 IP 地址),则 privateCount++ 统计私有 IP 地址数量*/
    } else if (num >= 192 && num <= 223) {
        //C 类 IP 地址的范围是 192.0.0.0 到 223.255.255.255
        Ccount++;//如果 num 在这个范围内,Ccount++ 统计 C 类 IP 地址数量
        if (stoi(ip[0]) == 192 && stoi(ip[1]) == 168 && stoi(ip[2]) >= 0 &&
                stoi(ip[2]) <= 255) privateCount++;
        /*如果该 IP 地址是 192.168.x.x(即私有 IP 地址),
        则 privateCount++ 统计私有 IP 地址数量*/
    } else if (num >= 224 && num <= 239) Dcount++;
    //D 类 IP 地址的范围是 224.0.0.0 到 239.255.255.255,用于组播
    //如果 num 在这个范围内,Dcount++ 统计 D 类 IP 地址数量
    else if (num >= 240 && num <= 255) Ecount++;
    //E 类 IP 地址的范围是 240.0.0.0 到 255.255.255.255,保留地址
    //如果 num 在这个范围内,Ecount++ 统计 E 类 IP 地址数量
}

/*通过getline逐行读取输入的IP和掩码
每行数据以"~"作为分隔符,前半部分是IP地址,后半部分是子网掩码
程序会先对每行数据进行合法性检查(字符是否为合法的数字、是否包含"~"、IP是否以"."结尾等)
然后将IP和掩码分别存储在ip和mask向量中
检查IP地址和掩码是否合法,如果合法则进行分类统计*/
int main() {
    string str;
    while (getline(cin, str)) {
        if(str.find_first_not_of("0123456789.~")!=string::npos) {
            errorCount++;
            continue;
        }
        //检查输入是否只包含数字、点(.)和波浪线(~),这些是合法的字符
        /*str.find_first_not_of("0123456789.~") 
        会返回第一个不在给定字符集 "0123456789.~" 中的字符的索引,
        如果找到了非法字符,find_first_not_of 会返回非 string::npos,
        说明输入中包含非法字符,
        这时增加错误计数(errorCount++)并跳过当前循环,继续处理下一个输入*/

    if(str.find("~")==string::npos) {
        errorCount++;
        continue;
    }
    //确保输入的字符串包含波浪线 ~,它用于分隔 IP 地址和子网掩码
    /*str.find("~") 用于查找波浪线的位置
    如果找不到波浪线(find 返回 string::npos),则说明输入格式不正确,
    增加错误计数,并继续处理下一行输入*/
        
    if(str.rfind(".")==str.size()-1)  {
        errorCount++;
        continue;
    }
    //确保输入的字符串不以点(.)结尾
    /*str.rfind(".") 查找字符串中最后一个点的位置
    如果最后一个字符是点(即 rfind 返回的索引等于字符串长度减去 1),
    则说明格式错误,增加错误计数并跳过此输入*/
       
        ip.clear();
        mask.clear();
        //在处理每一行输入之前,先清空 ip 和 mask 容器
        //用来存储提取出的 IP 地址和子网掩码

        int start = 0;
        int index = 0;

        str += ".";//给字符串末尾添加一个点(.)

        //提取 IP 地址部分
        for (; index < str.size(); index++) {
            //这是一种典型的 for 循环形式,它没有在初始化部分定义 index 变量
            //因为 index 变量在外部已经定义并初始化

            if (str[index] == '.') {
                string tmp = str.substr(start, index - start + 1);
            /*start 是子字符串的起始位置,index 是当前的 . 字符的位置,
            那么 index - start + 1 计算出的是子字符串的长度(包括 . 字符)*/

                tmp.pop_back();
                //该函数会移除 tmp 字符串的最后一个字符,这里是去掉末尾的 .

                start = index + 1;
                //这里的 start 是用于记录下一个部分的起始位置,
                //index + 1 表示从当前 . 后的位置开始,作为下一个部分的起点

                ip.push_back(tmp);
    /*这行代码将当前提取的子字符串 tmp(即去除 . 后的部分)推入 ip 容器中,
    ip 是一个 vector<string>,用于存储分割后的字符串部分*/
            }
            /*循环逐个字符检查,如果遇到 .,
            则提取当前段(使用 substr 截取子字符串),
            并去掉最后的点(使用 pop_back()),将该段添加到 ip 数组*/

            if (str[index] == '~') {
                string tmp = str.substr(start, index - start + 1);
                tmp.pop_back();
                start = index + 1;
                ip.push_back(tmp);
                break;
            }
            /*果遇到 ~,则表示接下来是子网掩码的开始,
            将前面的部分添加到 ip,并停止解析IP地址*/
        }
        /*for 循环遍历字符串中的每个字符
        如果遇到点(.),就将当前位置前的部分提取出来作为一个子字符串,
        存储到 ip 容器中
        每提取一次,就更新 start 为下一个部分的起始位置
        一旦遇到波浪线 ~,
        就将 ~ 之前的部分也提取出来并存入 ip,然后退出循环*/

        //提取子网掩码部分
        for (; index < str.size(); index++) {
            if (str[index] == '.') {
                string tmp = str.substr(start, index - start + 1);
                tmp.pop_back();
                start = index + 1;
                mask.push_back(tmp);
            }
        }
        /*第二个 for 循环继续遍历字符串,
        提取从波浪线 ~ 后开始的部分,直到字符串结束
        提取出的部分存入 mask 容器*/

        //忽略特殊情况
        if (ip[0] == "0" || ip[0] == "127") continue;
        //如果IP地址的第一个部分是 0 或 127(分别代表局域网地址和环回地址)
        //则跳过当前输入

        //判断是不是合法IP或者掩码
        if(!isValidIp(ip)) errorCount++;
        //调用 isValidIp(ip) 函数检查IP地址是否合法

        if(!isValidMask(mask)) errorCount++;
        //调用 isValidMask(mask) 函数检查子网掩码是否合法
        //如果任何一个不合法,则增加错误计数

        if(isValidIp(ip)&&isValidMask(mask)){
            ipClass(ip);
        }
    //如果IP地址和子网掩码都合法,则调用 ipClass(ip) 函数进行IP地址分类统计
    }
    cout << Acount << " " << Bcount << " " << Ccount << " " << Dcount << " " <<
         Ecount << " " << errorCount << " " << privateCount << endl;
         /*程序会输出以下几个统计结果:
        A类、B类、C类、D类、E类IP的数量
        错误输入的数量
        私有IP的数量*/
}

这段代码用于处理IP地址和子网掩码

1. IP地址(Internet Protocol Address)

IP地址是指在网络中分配给每台设备的唯一地址,它用于设备之间的通信;IP地址分为两个版本:

  • IPv4地址:是最常见的IP地址格式,由4个数字组成,每个数字的范围从0到255(例如:192.168.1.1);IPv4地址通常写成4个以点分隔的数字,如 192.168.0.1,每个数字代表一个字节(8位)

  • IPv6地址:为了应对IPv4地址空间耗尽问题,IPv6地址被引入,采用128位地址,由8组16进制数字组成,每组数字用冒号分隔(例如:2001:0db8:85a3:0000:0000:8a2e:0370:7334

IPv4地址分类

IPv4地址根据首字节的值,可以分为五类:

  • A类(1.0.0.0 到 126.255.255.255):用于大规模网络
  • B类(128.0.0.0 到 191.255.255.255):适用于中型规模网络
  • C类(192.0.0.0 到 223.255.255.255):用于小型网络
  • D类(224.0.0.0 到 239.255.255.255):用于多播地址
  • E类(240.0.0.0 到 255.255.255.255):保留地址,供未来使用

私有IP地址

某些IP地址范围被保留用于局域网(LAN)内使用,这些地址不会被路由到互联网:

  • A类私有地址:10.0.0.0 到 10.255.255.255
  • B类私有地址:172.16.0.0 到 172.31.255.255
  • C类私有地址:192.168.0.0 到 192.168.255.255

2. 子网掩码(Subnet Mask)

子网掩码用于确定IP地址的网络部分和主机部分,它的作用是将IP地址划分为网络地址和主机地址;子网掩码由32位的二进制数表示,通常以四个字节(8位)表示,如 255.255.255.0

子网掩码的工作原理:

  • 网络部分:子网掩码中的 1 部分表示网络地址
  • 主机部分:子网掩码中的 0 部分表示主机地址

例如:

  • 子网掩码:255.255.255.0
    • 对应二进制形式:11111111.11111111.11111111.00000000
    • 其中,前三个 255(即 11111111)表示网络部分,最后的 0(即 00000000)表示主机部分。

子网掩码的常见值

  • 255.0.0.0:A类网络
  • 255.255.0.0:B类网络
  • 255.255.255.0:C类网络

3. IP地址与子网掩码的关系

子网掩码和IP地址一起定义了一个网络,IP地址用来标识设备,子网掩码用来区分网络部分和主机部分;通过AND运算将IP地址与子网掩码进行运算,可以得到网络地址

例如,假设:

  • IP地址192.168.1.10
  • 子网掩码255.255.255.0

将这两个地址进行AND运算:

  • IP地址(二进制)11000000.10101000.00000001.00001010
  • 子网掩码(二进制)11111111.11111111.11111111.00000000

AND运算结果:

  • 网络地址(二进制)11000000.10101000.00000001.00000000 -> 192.168.1.0

所以,网络地址192.168.1.0,表示这个IP地址属于 192.168.1.0/24 网络,主机部分为 10

4. CIDR表示法

CIDR(Classless Inter-Domain Routing)表示法用于表示IP地址和子网掩码。例如,192.168.1.0/24 表示网络地址 192.168.1.0,并且子网掩码为 /24(即255.255.255.0)

5. IP地址的子网划分

根据子网掩码的不同,可以将一个IP地址划分为多个子网络;通过调整子网掩码的位数,可以创建多个子网,每个子网有独立的网络地址和主机地址。这对于网络的管理和扩展非常重要

总结

  • IP地址用于唯一标识网络中的设备,可以分为A、B、C、D、E类
  • 子网掩码定义了IP地址的网络部分和主机部分,用来区分不同的子网
  • IP地址和子网掩码一起确定了网络范围和设备的唯一性。在网络设计中,合理的子网划分可以有效地管理和分配IP地址

代码功能

  1. 合法性检查:程序首先检查输入的IP地址和子网掩码是否符合规范,它确保:

    • 每个IP段(0到255之间)都是合法的
    • 子网掩码的每个部分必须是标准值(如255、254、252等)并且符合递减规则
    • IP地址和掩码之间使用~分隔,并且字符串不以.结尾
  2. 分类统计:代码根据IP的第一段来判断其属于哪一类:

    • A类:1.0.0.0 到 126.255.255.255
    • B类:128.0.0.0 到 191.255.255.255
    • C类:192.0.0.0 到 223.255.255.255
    • D类:224.0.0.0 到 239.255.255.255(用于组播)
    • E类:240.0.0.0 到 255.255.255.255(保留地址)
    • 私有IP:特定的IP地址段(例如10.x.x.x172.16.x.x - 172.31.x.x192.168.x.x
  3. 错误处理:如果输入的IP地址或子网掩码不符合格式要求,则会增加错误计数

思路

  1. 数据存储:使用vector<string>来存储IP地址和子网掩码的每个部分;ip用于存储IP地址,mask用于存储掩码;通过分割字符串处理IP地址和掩码。

  2. 字符串检查:使用isValidStr()函数检查每个IP段是否在0到255之间,isValidIp()函数检查整个IP地址的合法性。isValidMask()函数则用于判断子网掩码是否合法;

  3. 输入处理:通过getline()逐行读取输入,并对每行数据进行格式验证;每行数据使用~分隔IP地址和子网掩码,程序会提取这两部分并检查其合法性

  4. 分类和计数:根据IP地址的第一个数字,判断其属于A类、B类、C类、D类或E类;还会检查IP是否为私有地址并进行相应的计数

代码结构

  1. 函数

    • isValidStr():检查字符串是否是有效的IP段(0-255)
    • isValidIp():检查IP地址的合法性
    • isValidMask():检查子网掩码的合法性
    • ipClass():根据IP地址的第一部分判断其类型(A、B、C、D、E),并计数私有IP
  2. 主函数

    • 循环读取输入,逐行解析并处理IP地址和子网掩码
    • 如果输入不合法,则跳过该行
    • 合法的IP和掩码会被分类并统计

改进建议

  1. 错误处理:当前代码没有处理stoi()可能抛出的异常(如无效的数字字符串),可以加上try-catch来避免程序崩溃

  2. 增强输入验证:除了检查输入字符是否合法,可能还需要验证更多的格式(例如IP段的顺序)

  3. 扩展功能:可以考虑添加更多的功能,比如输出IP地址和掩码的详细信息,或者生成对应的网络地址等

这段代码的主要任务是对输入的IP地址和子网掩码进行格式验证、分类统计和错误处理,是一个较为常见的网络编程任务


网站公告

今日签到

点亮在社区的每一天
去签到