【问题标题】:Number getting stored as special character in C++数字在 C++ 中存储为特殊字符
【发布时间】:2021-06-29 20:12:35
【问题描述】:
#include<fstream>
#include<string.h>
#include<iostream>
using namespace std;
class contact
{
    long long ph;
    unsigned char name[20],add[50],email[30];
    public:
    void create_contact()
    {
        cout<<"Phone: ";
        cin>>ph;
        cout<<"Name: ";
        cin.ignore();
        cin>>name;
        cout<<"Address: ";
        cin.ignore();
        cin>>add;
        cout<<"Email address: ";
        cin.ignore();
        cin>>email;
        cout<<"\n";
    }
    void show_contact()
    {
        cout<<endl<<"Phone Number: "<<ph;
        cout<<endl<<"Name: "<<name;
        cout<<endl<<"Address: "<<add;
        cout<<endl<<"Email Address : "<<email;
    }
    long long getPhone()
    {
        return ph;
    }
    unsigned char* getName()
    {
        return name;
    }
    unsigned char* getAddress()
    {
        return add;
    }
    unsigned char* getEmail()
    {
        return email;
    }
};
fstream fp;
contact cont;
void save_contact()
{
    fp.open("contactBook.txt",ios::out|ios::app);
    cont.create_contact();
    fp.write((char*)&cont,sizeof(contact));
    fp.close();
    cout<<endl<<endl<<"Contact Has Been Sucessfully Created...";
    getchar();

嘿,我是 C++ 以及这个社区的新手,在我一直在编写的代码中,联系人的电话号码被保存为随机特殊字符。这是我认为出现问题的代码的一半关于如何解决它的任何想法?这会有很大帮助。谢谢!

【问题讨论】:

  • 能否包含联系人的定义?答案很可能在那里。那你应该将流作为二进制打开,以避免换行符的有趣问题。
  • 如果contact 不是一个简单的struct 包含非常简单的数据(没有指针,没有类),那么你就会遇到一些严重的未定义行为。
  • 猜谜游戏:电话是一个长整数,您希望它以 ASCII 模式存储为以 10 位为基数的序列。好吧,整数存储在基数 2 中,write() 只是将内存表示放在文件中的变量上。在任何情况下,您都需要提供minimal reproducible example
  • 另外两个搜索词:“序列化”和“内存结构布局”。

标签: c++ file contacts dev-c++


【解决方案1】:

我认为您希望看到在您的文本文件中写出的电话号码类似于“15551234567”。但是,long long 不会以这种形式存储在内存中。它实际上存储为 64 位二进制整数。您描述的特殊字符可能是该整数的编码版本。如果你把数据读回来,你应该会发现它仍然是一个整数。

但是,还有一个问题。您在 fstream open 命令中缺少 ios::binary。每个ios 标志都为流注入了特定的行为:

  • ios::out - 表示这个流应该是一个可以写入字节的输出流
  • ios::app - 表示这个流应该以“追加”模式打开。这意味着它不会在您每次打开文件时擦除文件的内容,并且任何输出到流中的字节都会附加到文件的末尾。
  • ios::binary - 以二进制模式打开文件,当您想要输入/输出二进制数据时需要这种模式,而不仅仅是文本。

您想用ios::out | ios::app | ios::binary 打开文件。忘记二进制会导致非常难以调试的错误。

现在二进制模式有点麻烦。抱歉,这篇文章读了很久,但如果您了解它背后的历史,就更容易掌握这个标志。

早在计算的早期,关于如何将换行符写入文件存在分歧。这样打字机的日子,开始一个新的线路被分成两个动作。有“回车”将打字机的滑动位移回行首(这是动作的响亮部分),还有“换行”将纸张向上移动了一个位置。其中每一个都是单独的操作,因此它们在ASCII 中被赋予单独的字符,这是将文本写入字节字符串的最终方法之一。 8 位数字 10 编码换行符(又名LF),8 位数字 13 编码回车符(又名CR)。这将允许一个人做一些事情,比如过度输入,一个人输入一个字符(如字母)然后返回在顶部添加另一个字符(如重音)的技巧。你可以写à,首先输入a,做一个“回车”,然后写一个`,就像你在打字机上做的那样。

某些操作系统(例如 Windows)将下一行的开头编码为这两个字符,因此您会在文本文件中看到 CR LF。其他操作系统(如 Unix)认为在每行的末尾浪费一个宝贵的字节是不值得的,所以他们选择用 LF 来表示新行的开头。其他人(例如 Macintosh)决定将新行表示为 CR。没有人会同意。

为了解决这个问题,许多文件读/写 API 对这些字符进行了特殊处理。 fopenfstream 遵循一种模式,如果他们在文本文件中看到 CR LFCR,他们会在读取时默默地将其转换为 LF 字符。这使您可以读取每种文件类型。同样,如果它在写入时看到LF 字符,它会将其扩展为平台指定的新行应该是什么样的。这使您可以编写跨平台代码来编写文本文件,而无需注意每个平台上使用了哪个换行符!

但是,这会给二进制数据带来巨大的问题。考虑将数字 302,844,416 写为 32 位数字。在hexadecimal 中,我们将其写为0x120D0A00(十六进制是编程中编写数字的一种流行方式,因为每个字节都可以写成十六进制的两个字符)。问题在于数字的中间两个字节,0x0D 和 0x0A。在十进制中,它们是 13 和 10,您应该将它们识别为与 CRLF 相同的字节。

如果程序尝试在“文本模式”下读取该数字,它将看到 CR LF 对,并根据 C 规则将其转换为单个 LF。现在,我们的编号不是0x120D0A00,而是0x120A00XX,其中XX 是文件中的下一个字节。非常糟糕的事情!不仅此数据已损坏,而且您可能需要下一个字节来存储文件中接下来的任何内容!

ios::binaryfopen 的“b”标志解决了这个问题。他们告诉 C/C++ 数据将是二进制的。不会有任何要转换的新行。如果您将字节写入二进制流,它们会直接写入文件,无需任何巧妙的尝试来处理新行。

您的电话号码存储为long long,这是一种二进制整数格式。如果没有ios::binary,您将冒着数字发生的风险,其中包含CR LF 对,fstream 会损坏您的数据。 ios::binary 告诉 fstream 不要以这种方式弄乱数据。

【讨论】:

    猜你喜欢
    • 2023-03-26
    • 1970-01-01
    • 2015-06-14
    • 1970-01-01
    • 2014-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多