一文讲明白ACM输入输出模式

一文讲明白ACM输入输出模式

在线练习链接:牛客网OJ在线编程常见输入输出练习场

ACM输入输出模式是指在竞赛编程(ACM/ICPC)中常用的一种输入输出方式,它与普通控制台的输入输出有所不同。

输入模式

选择合适的输入方式

  • 判断输入的类型,是数字还是字符串,如果是数字,则可以使用cin标准输入流,如果是字符串,我们可以用getline()函数;
  • 判断是否知道输入数据的数量:
    • 如果数量已知,可以直接用for循环读取;
    • 如果数量未知,需要在循环输入中设置退出输入模式的条件;
使用cin还是getline进行输入
getline()函数:

getline() 是 C++ 标准库中的一个函数,用于从输入流中读取一整行字符串,直到遇到换行符(\n)或文件结尾。它通常用于从 cin (标准输入)或文件中读取一行字符串。

函数原型:

std::istream& getline (std::istream& is, std::string& str, char delim = '\n');
  • is 是输入流对象,可以是 std::cin 或文件流对象。
  • str 是一个字符串引用,用于存储从输入流中读取的字符串。
  • delim 是一个可选参数,表示结束字符,默认为换行符 ‘\n’。

std::stringstream也可以放在std::istream位置,例如getline(stringsteam& ss, string& str, char c)

注意的是:

  • getline()会保留读取的字符串中的所有字符,包括空格、制表符等空白字符
  • 如果输入流为空或遇到文件结尾,getline() 会返回 false,否则返回 true。(没必要退出循环时判断为空,如果为空则不会进入循环 while(getline(cin, s))
  • 读取的字符串不包含结束字符(默认为换行符 ‘\n’)。

如果一行中数字的数量是固定的,则使用cingetline()都可以,并且cin可能更方便;

例如:A+B(1)中,计算输入的两个数字之和;

输入:
1 5
10 20

每一行规定了只有两个数据,则使用cingetline()都可以;

如果一行中数字的数量是未知的,比如计算每一行数字的累加和中:

输入:
1 2 3 4 5
1 2 3
1 1

每一行的数据长度不固定,而且没有显式说明每一行数据的长度,此时使用cin明显变得复杂不可控,直接使用getline()读取一行字符串,然后字符串切割

如果显式告诉了每行的数据的个数,则cingetline()都可以,比如:

输入:
2 1 1
3 1 2 3
其中每行第一个数字是该行要累加的数据个数;

getline()更加全能,但是在getline()之后涉及字符串的切割、转换字符串为整型等一系列操作,所以使用时也要斟酌。

输入模式的退出

输入和输出是两个独立的部分,只有输入模式退出之后,才能进行输出;

例如:A+B(1)中,计算输入的两个数字之和中;

输入:
1 5
10 20

可以看出我们并不知道要输入几行数据,所以需要加入输入结束的判断逻辑,如果是使用getline()读取的字符串数据,则输入结束条件就是getline()获取到的为空行时结束输入,开始输出结果;

而在A+B(2)中,输入的第一行包括一个数据组数t,接下来每一行是两个正整数,计算两数之和;

输入:
2
1 5
10 20

此时就并不需要进行判断输入结束条件,而是直接使用for循环读取输入:for (int i = 0; i < t; i++) {读取数据,使用cin读取}

也有一些题目会给出输入退出条件,比如当输入0时,退出输入,只需要在输入后加入判断语句即可;

简而言之,如果知道输入的组数,则可以使用for循环控制,如果不知道输入的组数且没有显式的输入结束退出条件,只需要自己在循环中设置退出条件;

输入为字符串的处理

使用getline()获取到一行的字符串时,就涉及到字符串的处理;

例如:

输入:
1 2 3 4
1 2 3

我们使用getline()读取一行字符串,例如是"1 2 3 4",则需要将字符串按照空格进行切割成多个子串,这些子串还需要转换为整型进行计算;

字符串的处理方法多种多样,这里介绍4种常用的方法:

  1. 将字符串看成一个字符数组,然后遍历字符数组
  2. 使用std::stringstream字符串流进行处理(推荐使用);
  3. 使用**find()substr()函数**组合处理;
  4. 使用正则表达式进行匹配;
将字符串看作字符数组处理

例如:(以A+B(1)为例)

for (int j = s.size() - 1; j >= 0; j--) {
    if (s[j] == ' ') count = 1;
    else {
        res += (s[j] - '0') * count; 
        count *= 10;
    } 
}

注意事项:

  • 字符串要从右向左遍历,因为数字的低位靠右;
  • 字符串拼接直接使用加号运算符(重载后的运算符);
  • 注意字符串转整型的逻辑(按位处理);
使用std::stringstream进行处理

例如:(以A+B(1)为例)

#include <sstream>

// 方法2: 使用std::sstream;
stringstream ss(s);
int a, b;
ss >> a >> b;
res = a + b;

stringstream是C++标准库中的一个类,它可以让你像使用输入/输出流一样来操作字符串。它继承自iostream类,因此具有和cin、cout类似的操作符>><<

stringstram对象ss的使用方法和cin类似;我们可以使用>>操作符从ss中提取整数,就像从cin中读取一样。

函数原型:

stringstream::stringstream(const string& str);

stringstream默认是按照空格进行切分字符串的,切分结果可以通过>>操作符提取,可以提取整型,也可以提取字符串;

如果字符串不是按照空格进行切分的,则不能使用stringstream进行切分;

比如题目字符串排序(3)中,数据用’,'而不是空格进行隔开,则此时无法直接使用stringstream获取到切割后的子串,而是联合getline()进行使用:

stringstream ss(s); // 只能切割为整型?不是
vector<string> strline;
string str;
// while (ss >> str) strline.push_back(str);// 按照逗号切割而不是空格;
while (getline(ss, str, ',')) { // 按照逗号切割; 注意getline在cin中只会按照\n切割,
    // cin是std::istream类型,而ss是std::stringsteam类型,所以是两个重载方法,参数不一致,不要混淆;
    strline.push_back(str);
}

注意如果是字符串连接,则使用加号运算符;如果是在字符串后面添加一个字符,则可以使用push_back,因为字符串本质就是一个vector<char>

使用find()substr()进行处理

std::string::find是C++标准库中的一个字符串查找函数,它返回指定子串在字符串中第一次出现的位置。如果没有找到,则返回string::npos
std::string::substr是一个字符串截取函数,它返回一个新字符串,该字符串是原字符串的一个子串。

例如:(以A+B(1)为例)

// 方法2:使用find和substr(切割字符串为子串)
size_t pos = s.find(' '); // 找到字符串中第一次出现空格处的索引,注意使用size_t而不是int类型;
int a = stoi(s.substr(0, pos)); // 注意stoi即string to int,此外注意substring的左闭右开;
int b = stoi(s.substr(pos + 1)); // 余下子串不必写结束位置;
res = a + b;

std::stoi函数是C++11新增的一个字符串转整数的函数,它的原型为:

int stoi(const string& str, size_t* idx = 0, int base = 10);

它将字符串str转换为整数,base参数指定进制(默认为10进制)。如果转换失败,它会抛出一个std::invalid_argument异常。idx参数是一个可选的输出参数,用于存储转换停止的位置。

注意find函数返回的数据类型是size_t而不是intsize_t是 C++ 标准库中的一种无符号整数类型,用于表示大小或计数。它的确切大小取决于操作系统和编译器的实现,但至少有足够的范围来表示当前机器的最大可能对象大小。使用size_t类型作为索引或者计数变量的好处:

  1. 避免下标越界;(无符号类型)
  2. 尺寸兼容性;(和使用的库函数、容器类进行尺寸匹配)
  3. 跨平台兼容性;
  4. 标准化;
使用正则表达式处理

例如:(以A+B(1)为例)

#include <regex>

// 方法3:使用正则表达式匹配;stl::regex;
regex re("\\s+"); // 正则表达式匹配一个或多个空白字符,\\是因为防止转义字符
sregex_token_iterator iter(s.begin(), s.end(), re, -1); // 创建分词迭代器
sregex_token_iterator end; // 结束迭代器
int num1 = std::stoi(*iter++); // 提取第一个单词,并转换为整数,迭代器自增到第二个单词位置
int num2 = std::stoi(*iter); // 提取第二个单词,并转换为整数
res = num1 + num2;

正则表达式(Regular Expression)是一种用于匹配字符串的模式(pattern)。它是由一些特殊字符和普通字符组成的字符串,可以用来描述、匹配或替换另一个字符串。

正则表达式常用的元字符:

  • . 匹配任意单个字符(除了换行符)
  • \d 匹配任意数字字符,等价于[0-9]
  • \D 匹配任意非数字字符,等价于[^0-9]
  • \w 匹配任意字母、数字或下划线字符
  • \W 匹配任意非字母、非数字和非下划线字符
  • \s 匹配任意空白字符(空格、制表符、换行符等)
  • \S 匹配任意非空白字符
  • ^ 匹配字符串的开始位置
  • $ 匹配字符串的结束位置
  • [] 匹配括号内的任意单个字符
  • [^] 匹配不在括号内的任意单个字符
  • | 匹配左右两个表达式中的任意一个
  • () 用于分组
  • +匹配前面的子表达式一次或多次
  • *匹配前面的子表达式零次或多次
  • ? 匹配前面的子表达式零次或一次
  • {n} 匹配前面的子表达式恰好 n 次
  • {n,} 匹配前面的子表达式至少 n 次
  • {n,m} ``匹配前面的子表达式至少 n 次,但不超过 m 次

正则表达式匹配实例

  • ^\d{3}\s+\d{3,8}$ 匹配连续的数字(区号)和数字(电话号码),例如"800 8888888"
  • \b\w+\b 匹配单词边界(一个或多个字母、数字和下划线)
  • \d{4}-\d{2}-\d{2} 匹配日期格式,例如"2023-05-28"
  • [a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+ 匹配电子邮件地址

输出模式

输入使用cout进行输出即可;一般情况下,输入数据经过处理的结果在数组中,只需要循环访问数组中元素然后依次cout即可;


对字符串进行排序

字符串排序(1)中,需要对输入的字符串进行排序,可以使用sort函数;

输入:
5
c d a bb e

可以使用stringstream轻易地将一行中切割好后的各个字符串保存在一个字符串向量中(也可以使用其他方法)

在得到一个字符串向量ss之后,要对字符串向量进行排序:

#include <algorithm> // sort函数头文件;

// 对字符串数组ss进行排序;
sort(ss.begin(), ss.end(), compareStrings);

compareStrings函数是一个自定义函数,返回布尔类型:

bool compareStrings (const string& a, const string& b) {
    return a < b;
}

此时即可实现字符串排序;


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

OutlierLi

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

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

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

打赏作者

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

抵扣说明:

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

余额充值