C++ C 字符串(显然)没有要显示的内容
C++ C-string has (apparently) no content to display
首先,我对编程和 Stack Overflow 都是新手。
我正在自学 Schaum 的 C++ 编程大纲,我对问题 8.24 有一些疑问(书中几乎每个问题都给出了解决方案,但我想知道为什么我的代码特别没有按预期工作)。
你应该得到一个 c 字符串和 return 给定的字符串,但它的所有标记都以相反的顺序排列(但保持标记本身的自然顺序)。
也就是说,给定“输入句子”,它会在屏幕上显示“句子 a Enter”。
我的代码如下:
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
char line1[100];
cout << "Enter a sentence (enter \".\" to terminate input):\n";
cin.getline(line1,100,'.');
char line2[strlen(line1) + 1]; // we add 1 for the empty char that ends every c string
int char_count = strlen(line1); //strlen() does not include the empty char
char* p = strtok(line1," ");
while (p)
{
char_count -= strlen(p); // we substract p's len to start adding its chars
for (int i = 0; i <= strlen(p); i++)
line2[char_count + i] = p[i]; // we then add the chars themselves
if ((char_count - 1) > 0)
line2[--char_count] = ' '; // a blanck space is needed between the different tokens
p = strtok(NULL, " ");
}
cout << "\n" << line2 << "\n";
}
不幸的是,代码在很多方面都是错误的。最明显的是单词反转过程的晦涩(以及它与单词迭代混合的事实)。
根据评论者的说法,您没有使用 C++。在 C++ 中,它会相当简单:
#include <algorithm>
#include <iostream>
#include <string>
void reverse_words(std::string& s) {
/* starting position of the word */
size_t last_pos = 0;
do {
/* find the end of current word */
size_t end_pos = std::min( s.find(' ', last_pos + 1), s.size() );
/* reverse one word inplace */
std::reverse(s.begin() + last_pos, s.begin() + end_pos);
/* advance to the begining of the next word */
last_pos = end_pos + 1;
} while (pos != std::string::npos);
std::reverse(s.begin(), s.end());
}
int main()
{
std::string s = "This is a sentence";
reverse_words(s);
std::cout << s << std::endl;
}
希望你能看出方法的本质:依次找到每个单词的开头和结尾,将单词中的字母顺序倒序,最后将整个字符串倒序。
现在,回到 C-string 问题。您可以将 std::string::find
调用替换为 strtok
并编写专门用于 C 字符串的 std::reverse
版本(整个字符串或其部分的反转比反转词序更简单,这也是推荐的练习)。
从一个更简单的程序开始,该程序使用 strtok
打印出整数对(每个单词的 start_pos 和 end_pos)。然后编写一个 reverse
程序并对其进行测试。最后把这个词迭代和reverse
结合起来。我个人认为这是确保您的实施正确的唯一方法 - 确定其每个部分并能够单独测试每个部分。
自从最初编写那本书以来,C++ 已经有了很多改进,我们现在可以用更干净、更安全的方式来完成它。我们将问题分为两部分:
- 将字符串转换为标记列表的函数
- main函数,读取字符串;反转它;并打印出来。
这些函数将是 tokenize
,其中 return 是 string_view
和 main
的向量。 string_view
只是一个 class,它存储一个指向其他字符串的指针和大小。它很高效,因为它不会复制字符串或分配任何内存。在这种情况下,它是完成这项工作的正确工具,因为我们要分解现有的字符串。
#include <string_view>
#include <string>
#include <iostream>
#include <vector>
#include <algorithm>
auto tokenize(std::string_view line) {
std::vector<std::string_view> tokens;
for (size_t token_size = line.find(' ');
token_size != line.npos;
token_size = line.find(' '))
{
tokens.push_back(line.substr(0, token_size));
line.remove_prefix(token_size + 1);
}
tokens.push_back(line);
return tokens;
}
int main() {
std::string line;
std::getline(std::cin, line);
auto tokens = tokenize(line);
std::reverse(tokens.begin(), tokens.end());
for(auto token : tokens) {
std::cout << token << ' ';
}
std::cout << std::endl;
}
解释标记化
Tokenize 将 string_view 作为输入,return 是一个标记列表。 line.find(' ')
将查找 space。如果它找到一个,它将 return space 的位置;否则,它将 return line.npos
(这基本上是最大可能的大小)。
对于我们找到的每个标记,我们
- 通过
view.substr(0, token_size)
获取令牌
- 通过
tokens.push_back
将标记添加到向量中
然后,我们通过删除第一个标记和相应的 space 来更新行。这是line.remove_prefix(token_size + 1);
一旦不再有 spaces,我们将使用 tokenize.push_back(line);
将行的剩余部分添加到向量中,然后我们将 return 标记向量.
解释主要内容
我们可以通过std::getline(std::cin, line);
获取该行,它会从cin
中读取一行并将其放入我们给它的变量(line)中。之后,我们可以使用我们编写的 tokenize
函数读取该行中的所有标记。我们将通过 std::reverse
反转令牌向量,然后打印出所有令牌。
谢谢你们每一个人。
看到您的回答,我学到了很多关于良好编程的知识(包括语法和解决问题本身的原始方法,如 Viktor 的)。
如果我没有给出适当的反馈,我深表歉意,但我(仍然)不熟悉 Stack 的习俗和“'policies'”。
首先,我对编程和 Stack Overflow 都是新手。
我正在自学 Schaum 的 C++ 编程大纲,我对问题 8.24 有一些疑问(书中几乎每个问题都给出了解决方案,但我想知道为什么我的代码特别没有按预期工作)。
你应该得到一个 c 字符串和 return 给定的字符串,但它的所有标记都以相反的顺序排列(但保持标记本身的自然顺序)。 也就是说,给定“输入句子”,它会在屏幕上显示“句子 a Enter”。
我的代码如下:
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
char line1[100];
cout << "Enter a sentence (enter \".\" to terminate input):\n";
cin.getline(line1,100,'.');
char line2[strlen(line1) + 1]; // we add 1 for the empty char that ends every c string
int char_count = strlen(line1); //strlen() does not include the empty char
char* p = strtok(line1," ");
while (p)
{
char_count -= strlen(p); // we substract p's len to start adding its chars
for (int i = 0; i <= strlen(p); i++)
line2[char_count + i] = p[i]; // we then add the chars themselves
if ((char_count - 1) > 0)
line2[--char_count] = ' '; // a blanck space is needed between the different tokens
p = strtok(NULL, " ");
}
cout << "\n" << line2 << "\n";
}
不幸的是,代码在很多方面都是错误的。最明显的是单词反转过程的晦涩(以及它与单词迭代混合的事实)。
根据评论者的说法,您没有使用 C++。在 C++ 中,它会相当简单:
#include <algorithm>
#include <iostream>
#include <string>
void reverse_words(std::string& s) {
/* starting position of the word */
size_t last_pos = 0;
do {
/* find the end of current word */
size_t end_pos = std::min( s.find(' ', last_pos + 1), s.size() );
/* reverse one word inplace */
std::reverse(s.begin() + last_pos, s.begin() + end_pos);
/* advance to the begining of the next word */
last_pos = end_pos + 1;
} while (pos != std::string::npos);
std::reverse(s.begin(), s.end());
}
int main()
{
std::string s = "This is a sentence";
reverse_words(s);
std::cout << s << std::endl;
}
希望你能看出方法的本质:依次找到每个单词的开头和结尾,将单词中的字母顺序倒序,最后将整个字符串倒序。
现在,回到 C-string 问题。您可以将 std::string::find
调用替换为 strtok
并编写专门用于 C 字符串的 std::reverse
版本(整个字符串或其部分的反转比反转词序更简单,这也是推荐的练习)。
从一个更简单的程序开始,该程序使用 strtok
打印出整数对(每个单词的 start_pos 和 end_pos)。然后编写一个 reverse
程序并对其进行测试。最后把这个词迭代和reverse
结合起来。我个人认为这是确保您的实施正确的唯一方法 - 确定其每个部分并能够单独测试每个部分。
自从最初编写那本书以来,C++ 已经有了很多改进,我们现在可以用更干净、更安全的方式来完成它。我们将问题分为两部分:
- 将字符串转换为标记列表的函数
- main函数,读取字符串;反转它;并打印出来。
这些函数将是 tokenize
,其中 return 是 string_view
和 main
的向量。 string_view
只是一个 class,它存储一个指向其他字符串的指针和大小。它很高效,因为它不会复制字符串或分配任何内存。在这种情况下,它是完成这项工作的正确工具,因为我们要分解现有的字符串。
#include <string_view>
#include <string>
#include <iostream>
#include <vector>
#include <algorithm>
auto tokenize(std::string_view line) {
std::vector<std::string_view> tokens;
for (size_t token_size = line.find(' ');
token_size != line.npos;
token_size = line.find(' '))
{
tokens.push_back(line.substr(0, token_size));
line.remove_prefix(token_size + 1);
}
tokens.push_back(line);
return tokens;
}
int main() {
std::string line;
std::getline(std::cin, line);
auto tokens = tokenize(line);
std::reverse(tokens.begin(), tokens.end());
for(auto token : tokens) {
std::cout << token << ' ';
}
std::cout << std::endl;
}
解释标记化
Tokenize 将 string_view 作为输入,return 是一个标记列表。 line.find(' ')
将查找 space。如果它找到一个,它将 return space 的位置;否则,它将 return line.npos
(这基本上是最大可能的大小)。
对于我们找到的每个标记,我们
- 通过
view.substr(0, token_size)
获取令牌
- 通过
tokens.push_back
将标记添加到向量中
然后,我们通过删除第一个标记和相应的 space 来更新行。这是line.remove_prefix(token_size + 1);
一旦不再有 spaces,我们将使用 tokenize.push_back(line);
将行的剩余部分添加到向量中,然后我们将 return 标记向量.
解释主要内容
我们可以通过std::getline(std::cin, line);
获取该行,它会从cin
中读取一行并将其放入我们给它的变量(line)中。之后,我们可以使用我们编写的 tokenize
函数读取该行中的所有标记。我们将通过 std::reverse
反转令牌向量,然后打印出所有令牌。
谢谢你们每一个人。 看到您的回答,我学到了很多关于良好编程的知识(包括语法和解决问题本身的原始方法,如 Viktor 的)。 如果我没有给出适当的反馈,我深表歉意,但我(仍然)不熟悉 Stack 的习俗和“'policies'”。