我的代码:
#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地址
代码功能
合法性检查:程序首先检查输入的IP地址和子网掩码是否符合规范,它确保:
- 每个IP段(0到255之间)都是合法的
- 子网掩码的每个部分必须是标准值(如255、254、252等)并且符合递减规则
- IP地址和掩码之间使用
~
分隔,并且字符串不以.
结尾
分类统计:代码根据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.x
,172.16.x.x - 172.31.x.x
,192.168.x.x
)
错误处理:如果输入的IP地址或子网掩码不符合格式要求,则会增加错误计数
思路
数据存储:使用
vector<string>
来存储IP地址和子网掩码的每个部分;ip
用于存储IP地址,mask
用于存储掩码;通过分割字符串处理IP地址和掩码。字符串检查:使用
isValidStr()
函数检查每个IP段是否在0到255之间,isValidIp()
函数检查整个IP地址的合法性。isValidMask()
函数则用于判断子网掩码是否合法;输入处理:通过
getline()
逐行读取输入,并对每行数据进行格式验证;每行数据使用~
分隔IP地址和子网掩码,程序会提取这两部分并检查其合法性分类和计数:根据IP地址的第一个数字,判断其属于A类、B类、C类、D类或E类;还会检查IP是否为私有地址并进行相应的计数
代码结构
函数:
isValidStr()
:检查字符串是否是有效的IP段(0-255)isValidIp()
:检查IP地址的合法性isValidMask()
:检查子网掩码的合法性ipClass()
:根据IP地址的第一部分判断其类型(A、B、C、D、E),并计数私有IP
主函数:
- 循环读取输入,逐行解析并处理IP地址和子网掩码
- 如果输入不合法,则跳过该行
- 合法的IP和掩码会被分类并统计
改进建议
错误处理:当前代码没有处理
stoi()
可能抛出的异常(如无效的数字字符串),可以加上try-catch
来避免程序崩溃增强输入验证:除了检查输入字符是否合法,可能还需要验证更多的格式(例如IP段的顺序)
扩展功能:可以考虑添加更多的功能,比如输出IP地址和掩码的详细信息,或者生成对应的网络地址等
这段代码的主要任务是对输入的IP地址和子网掩码进行格式验证、分类统计和错误处理,是一个较为常见的网络编程任务