数字在 C++ 中被存储为特殊字符

问题描述

#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++ 以及这个社区的新手,这是我一直在编写的代码,联系人的电话号码被保存为随机的特殊字符。这是我认为出现问题的代码的一半 关于如何修复它的任何想法?这会有很大帮助。谢谢!

解决方法

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

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

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

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

现在二进制模式有点害人。抱歉,阅读时间过长,但如果您了解其背后的历史,则更容易掌握此标志。

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

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

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

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

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

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

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