第一次不注释运行程序,test.txt文件成功写入数据,对象C也能成功获取从txt文件读入的数据,C->print()成功输出我是狗蛋~

第二次,先运行一遍,确保txt文件被写入数据.然后注释掉上面的代码再运行一遍想直接从文件读数据到对象C,结果文件读出来是空?

请问是为啥呢?

class Cat{
	public:
		string name;
		int age;
		
		Cat(){
			
		}
		Cat(string name,int age){
			this->age = age;
			this->name = name;
		}
		~Cat(){
			
		}
		
		void print(){
			cout<<"我叫"<<name<<endl; 
		} 
};

int main(){
	Cat* A = new Cat("狗蛋",3);
	Cat* C;
//	ofstream outfile;
//	outfile.open("test.txt",ios::out|ios::binary);
//	outfile.write((char*)&A, sizeof(A));
//	outfile.close();
	
	ifstream infile;
	infile.open("test.txt",ios::in|ios::binary);
	infile.read((char*)&C,sizeof(C));
	infile.close();
	C->print();
}

不太熟悉C++,不过应该类似的感觉。

从这段程序的角度分析,&C 得到的应该是 C 这个指针对象本身的地址吧,这里写入的文件,保存的也只是这个指针变量本身用的空间的一个地址。

用 VSCode 的调试,感觉还是蛮清晰的,让程序停在结尾,然后检查此时输出的 test.txt ,可以看到, test.txt 保存的是从内存 copy 过来的一个小端方式存储的数据。。

注释掉了前面的写入部分之后,读取的 test.txt 是上一次运行得到的内存地址,然后将这个内存地址写到指针 C 所属于的内存空间。因为程序每次启动的时候,操作系统为这个程序准备的进程分配的内存的地址是不一样的,所以,强行套用 test.txt 里面的内存地址的话,可能会请求访问不该访问的内存,然后就不正常了。

总结来说,问题应该不在文件读取这一块,大概是用文件存什么这一步可能没思考清楚??

    0x0001 猴哒!蟹蟹~我去试一试~话说感觉VSCode调试好像方便一点诶,之前接受安利看了翁恺老师的C语言教程就一直在用dev c++?

    0x0001 我回去试了一下,把代码改成了这样子

    int main(){
    	Cat A("狗蛋",3);
    	Cat C;
    	
    	ofstream outfile;
    	outfile.open("test.txt",ios::out|ios::binary);
    	outfile.write((char*)&A, sizeof(A));
    	outfile.close();
    	
    	ifstream infile;
    	infile.open("test.txt",ios::in|ios::binary);
    	infile.read((char*)&C,sizeof(C));
    	infile.close();
    	C.print();
    }

    那个我根据层主的建议把原来的指针换成了Cat对象
    我的理解是每次打开程序都新创建对象 A 和 C,write操作是在文件中写入从(char*)&A地址开始的sizeof(A)的数据。然后read就是从文件读出sizeof(C)的数据到(char*)&C地址。所以现在写入文件的应该就是对象的内容了吧,但是还是出现了同样的问题?(我去下个VSCode再研究研究:)

      read和write是二进制的读取与写入,是直接把对象对应的内存写入到对应的文件里。

      但是你的自定义类中有string对象,而string内部的内存是动态分配的,类似于如下的类

      class Foo {
      public:
          Foo() {
              arr = new char[10];
          }
          ~Foo() {
              delete[] arr;
          }
      
      private:
          char* arr;
      };

      那么在此类中,会对应写入文件里的只有变量arr的值,指向了一个已分配内存。在你下一次从文件中读出时,就会指向未分配内存,产生未定义行为 (undefined behavior)。

      解决的办法就是不要这样做,将一个STL对象的内存写入文件是无意义的,实际的效果与编译器实现相关。

      如果不希望因为以文本格式储存浪费空间,可以考虑使用string.c_str(),并重载自定义类的<<>>

        如果真的想把这个结构体“导出”,建议还是用C的风格写吧~

        字符串可以用固定的长度的char实现,这样就可以直接fread/fwrite读写啦。参考代码如下:

        #include "stdio.h"
        #include "string.h"
        
        typedef struct {
          char name[20]; // Name: 20Bytes char
        	int age; // Age: 4Bytes int
        } Cat;
        
        int main() {
          // 导出
          Cat *cat = (Cat *)malloc(sizeof(Cat));
          strcpy(cat->name, "Mimi");
          cat->age = 1;
        
          FILE *fp = fopen("./out.cat", "wb+");
          fwrite(cat, sizeof(Cat), 1, fp);
          fclose(fp);
        
          free(cat);
        
          // 导入
          cat = (Cat *)malloc(sizeof(Cat));
        
          fp = fopen("./out.cat", "rb+");
          fread(cat, sizeof(Cat), 1, fp);
          fclose(fp);
        
          printf("name:\t%s\n", cat->name);
          printf("age:\t%d\n", cat->age);
        
          free(cat);
        
          return 0;
        }

        实际的运行结果与导出数据:

        你可以发现,这个代码正常运行,导出的文件 out.cat 的 0x00~0x14 之间的是 name 对应的内容。至于为什么 0x05 以后的空间存在非0的数,这是因为导出前内存没有memset全部置0啦,不过不会影响字符串的读入(char[]的字符串靠 \0 判定末尾)。

        作为对比,你可以看看不定长度的字符串的写法:

        #include "stdio.h"
        #include "string.h"
        
        typedef struct {
          char name[20]; // Name: 20Bytes char
        	int age; // Age: 4Bytes int
        } Cat;
        
        int main() {
          // ## 导出
          Cat *cat = (Cat *)malloc(sizeof(Cat));
          strcpy(cat->name, "Mimi");
          cat->age = 1;
        
          FILE *fp = fopen("./out2.cat", "wb+");
        
          // 先写出age
          fwrite(&cat->age, sizeof(cat->age), 1, fp);
        
          // 再计算 name 的长度 并写出
          size_t name_len = strlen(cat->name) + 1;
          fwrite(&name_len, sizeof(name_len), 1, fp);
        
          // 最后写出 name 的数据
          fwrite(cat->name, 1, name_len, fp);
        
          fclose(fp);
        
          free(cat);
        
          // ## 导入
          cat = (Cat *)malloc(sizeof(Cat));
        
          fp = fopen("./out2.cat", "rb+");
        
          // 按照导出规则,先导入age
          fread(&cat->age, sizeof(cat->age), 1, fp);
        
          // 再读取name字符串长度
          fread(&name_len, sizeof(name_len), 1, fp);
        
          // 最后读取 name_len 长的name字符串
          fread(cat->name, 1, name_len, fp);
          fclose(fp);
        
          printf("name:\t%s\n", cat->name);
          printf("age:\t%d\n", cat->age);
        
          free(cat);
        
          return 0;
        }

        运行与导出数据:

        可以发现,的确是有节省到很多空间,不过这个代码写起来其实并不是很简洁啦。

        所以如果真的在C/C++写个文件导入导出,最好还是基础类型OR定长的数组,不然可以考虑用JSON之类的格式哈哈。

        本来想说点什么,没想到楼上的dalao出手这么快,狗尾续貂补点细节吧:

        C++标准库的string实现一般来说是Rhyster 的方法,用块动态分配内存解决:
        clang的cxx标准库实现(github镜像 commit 7593e799d293c28618fb67b7a2951786dfd18bef)
        大概长这样(标准库乃劝退大坑,没事不要乱翻)

        struct string {
            int string_size;//字符串长度
            int buffer_capacity;//内存空间的容量
            char* data;//数据本身
            //可能的内存分配相关的必要数据......
        };

        所以,如果写成这样:

        int main(){
        	Cat A("妈妈说名字一定要长长长长,一寸长一寸强",3);
        	Cat C;
        
        	ofstream outfile;
        	outfile.open("test.txt",ios::out|ios::binary);
        	outfile.write((char*)&A, sizeof(A));
        	outfile.close();
        	
        	ifstream infile;
        	infile.open("test.txt",ios::in|ios::binary);
        	infile.read((char*)&C,sizeof(C));
        	infile.close();
        	C.print();
        }

        输出,然后注释掉输出部分,再读,系统就会因为访问未分配内存然后崩溃。

        但实际上有些时候标准库会做一个叫”短字符串优化“的操作,如果你的整个字符串比这个struct还小,标准库就会直接把你的字符串存进这个struct里,大概长这样:

        typedef string long_version;
        struct short_version{
            int size;
            char values[sizeof(long_version)-sizeof(int)] ;
        };
        
        class optimized_string{
        private:
          union{
            long_version l;
           short_version s;
          }value;
        
        public:
          optimized_string(char* val){
            if(strlen(val)<*some value*){
            //用优化版本
            }else{
             //用长版本
            }
         }
        };

        这时候,如果名字叫“狗蛋”这种很短的字符串的话,标准库就会直接存进去,变成朴素数组,然后IO就能成功。

        clang,MSVC,g++,icc,各家编译器的标准库的实现都可能不同,
        所以直接存储字符串内存内容这种行为就叫做未定义行为,
        标准不关心,程序员也最好不要依赖。

        我来仰望楼上各位大牛。。。

        其实我想说, 如果想要把对象导出到文件, 一个比较靠谱的方法应该就是导出JSON??
        https://github.com/nlohmann/json c++的一个库
        Rust中, 我们可以通过自动推导宏!实现对象到Json的转换!

        比如, 在一个Request Handler里边, 我们可以这么写:

        然后此时, Json<Form>这个对象继承了所有Form的方法, 只是类型不一样...
        当然一些具体的细节, 比如循环引用之类的就具体问题具体分析了...

        哇塞,学习辽学习辽? 多谢各位大佬~

        还是给个例子吧!.. 2333
        以下的是定义一个用户类型, 然后将其序列化! 接着从序列化出来的字符串中恢复出原来的对象.

        运行结果....

        来水一个:
        C艹的文件操作是可以用流的方式的,超个连接:https://blog.csdn.net/lightlater/article/details/6364931
        你那个例子可以改成像这样:

        int main(){
            //Test* test = new Test();
            Cat test;
            char* tc = (char*)&test;
            cout<<hex<<(unsigned long)tc<<endl;
        
            ofstream of;
            of.open("test.txt",ios::out|ios::binary);
            //of.write(tc,sizeof(tc));  //有点麻烦
            of<<&test;  //比较方便
            of.close();
        
            return 0;
        }

        结果:

          Tover
          深夜翻车现场,我好像理解错你的意思了。。。你是想写入A的地址吗?这样的话最好还是先转成unsigned long然后再输出(这样的话会好理解一点),改一下代码:

          int main(){
              //Test* test = new Test();
              Cat test;
              unsigned long addr = (unsigned long)&test;
              cout<<hex<<addr<<endl;
          
              ofstream of;
              of.open("test.txt",ios::out|ios::binary);
              //of.write((char*)&addr,sizeof(unsigned long)); 
              of<<(char*)&addr;
              of.close();
          
              return 0;
          }

          结果:

          可以看出在我的电脑中,数据是小端存储

          看见大佬们的精彩回答,我来说说我的拙见。。。同意Rhyster 的看法,我觉得代码应该这样写。。。

          #include <iostream>
          #include<fstream>
          #include <ostream>
          
          class Object{
          public:
          	char name[10];
          	int number;
          	Object(){
          		memset(name,0,10);
          		number = 0;
          	}
          	Object(char* n,int num){
          		strncpy(name,n,10);
          		number = num;
          	}
          	void Print(){
          		printf("name = %s\nnumber = %d\n",name,number);
          	}
          };
          
          bool is_empty(std::ifstream& pFile)
          {
              return pFile.peek() == std::ifstream::traits_type::eof();
          }
          
          int Restore(Object* obj){
          	int i = 0;
          	std::ifstream file("test",std::ios::in | std::ios::binary);
          	while(!is_empty(file)){
          		std::cout<<"Restore"<<std::endl;
          		file.read((char*)(obj+i),sizeof(Object));
          		i++;
          	}
          	return i;
          }
          
          bool Save(Object* obj,int count){
          	int i = 0;
          	std::ofstream file("test",std::ios::out | std::ios::binary);
          	while(i < count){
          		std::cout<<"Save"<<std::endl;
          		file.write((char*)(obj+i),sizeof(Object));
          		i++;
          	}
          	return true;
          }
          
          int main(){
          	Object* obj = new Object[5];
          	int read = Restore(obj);
          	char name[] = "Ha";
          	name[2] = read + '0';
          	obj[read++] = Object(name,0);
          	printf("read = %d\n",read);
          	for(int j = 0 ;j < read; j++)
          		obj[j].Print();
          	Save(obj,read);
          	return 0;
          }


          是能够正常工作的。。。(师兄我真是菜,写了真久。。。)
          具体错误的原因我认为是没有理解write,read的作用你存的是Cat类型指针的地址,使用的也是Cat类型指针的地址,并没有存Cat对象。
          详细使用方法请参考cppreference

          6 个月 后

          © 2018-2025 0xFFFF