C++面向对象程序设计-北京大学-郭炜【课程笔记(九)】

开始课程:P33 1_1. 输入输出流相关的类
课程链接:程序设计与算法(三)C++面向对象程序设计 北京大学 郭炜
课程PPT:github提供的对应课程PPT

1、输入输出流相关的类

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

1.1、标准输出重定向

基础知识:在C++中,stdout代表标准输出流。它是指向标准输出设备的输出流,通常是屏幕或终端。当你使用std::cout进行输出时,实际上是向stdout流中写入数据。类似地,printf()函数也将输出写入到stdout流中。

#include<iostream>
using namespace std;

int main()
{
    int x,y;
    cin >> x >> y;
    freopen("test.txt", "w", stdout);  // 将标准输出重定向到test.txt文件
    if(y==0) // 除数为0则直接在屏幕/终端上输出错误信息
    {
        cerr << "error" << endl;
    }
    else
        cout << x/y; // 输出结果到test.txt文件中
    
    return 0;
}

请添加图片描述

1.2、标准输入重定向

#include<iostream>
using namespace std;

int main()
{
    double f; int n;
    freopen("test.txt", "r", stdin); //cin 被改为从test.txt中读取数据
    cin >> f >> n;
    cout << f << "," << n << endl;

    return 0;
}

请添加图片描述

1.3、判断输入流结束

注意事项:

  • while(cin>>x):应该是文件中内容输出结束后,cin>>x返回为False,即循环结束。但是我们周到istream的右移运算符的返回值为cin(即* this),,为什么能返回布尔型False呢!
  • 这里用到了强制类型转换符重载(下面做详细解释)。
    请添加图片描述

1.3.1、强制类型转换符重载

在C++中,你可以通过重载类型转换运算符来自定义类对象之间的类型转换。这种转换运算符被称为类型转换函数或转换运算符。以下是几种可以重载的类型转换运算符:

  • 1、转换为目标类型的类型转换函数:这种类型的类型转换函数将类对象转换为指定类型。它们的声明形式如下:
operator type() const;

其中type是你想要将类对象转换为的目标类型。这样的重载允许你在使用赋值操作符或传递参数时,隐式地将类对象转换为目标类型。

  • 2、转换为布尔类型的类型转换函数:这种类型的类型转换函数将类对象转换为布尔类型,通常用于条件测试。它们的声明形式如下:
explicit operator bool() const;

explicit关键字用于防止隐式转换,确保转换只在需要时发生。

举例来说,假设你有一个名为MyClass的类,你可以重载这些转换运算符以便让MyClass对象能够以期望的方式进行类型转换。下面是一个简单的示例:

#include <iostream>

class MyClass {
    int value;
public:
    MyClass(int val) : value(val) {}

    // 转换为int类型
    operator int() const {
        return value;
    }

    // 转换为布尔类型
    explicit operator bool() const {
        return value != 5;  // 输出为:intValue: 5  boolValue: 0
        return value != 1;  // 输出为:intValue: 5  boolValue: 1
    }
};

int main() {
    MyClass obj(5);

    // 隐式转换为int类型
    int intValue = obj;
    std::cout << "intValue: " << intValue << std::endl;

    // 显式转换为布尔类型
    bool boolValue = static_cast<bool>(obj);
    std::cout << "boolValue: " << boolValue << std::endl;

    return 0;
}

1.4、istream类的成员函数

请添加图片描述
请添加图片描述
例:

#include<iostream>
using namespace std;

int main()
{
    int x;
    char buf[100];
    cin >> x; 
    cin.getline(buf, 90);  // 将读取的一行序列存储到buf中
    cout << buf << endl;

    return 0;
}
OUT:
zhangbushi@zhangbushideair beida2 % ./4           
12 abcd
 abcd
zhangbushi@zhangbushideair beida2 % ./4
12

zhangbushi@zhangbushideair beida2 % 

1.4.1、cin.getline()函数

在C++中,cin.getline()函数从标准输入流中读取字符,并将它们存储到给定的字符数组中,直到达到指定的最大字符数(包括结束符’\0’)或者遇到指定的结束符(默认为换行符’\n’)为止。读取的字符包括结束符之前的所有字符,但不包括结束符本身。如果读取到达了指定的最大字符数,剩余的字符将被丢弃。它的一般形式如下:

istream& getline (char* s, streamsize count, char delim);

其中:

  • s是一个指向字符数组的指针,用于存储读取的字符序列。
  • count是要读取的最大字符数,包括最后的空字符(‘\0’)。
  • delim是可选参数,表示行的结束符,默认为换行符’\n’。
#include <iostream>

int main() {
    const int bufferSize = 100;
    char buffer[bufferSize];

    std::cout << "Enter a line of text: ";
    std::cin.getline(buffer, bufferSize);

    std::cout << "You entered: " << buffer << std::endl;

    return 0;
}

OUT:
beida2 % ./4
Enter a line of text: I love C++.
You entered: I love C++.

2、流操纵算子控制输出格式

  • 整数流的基数:流操纵算子dec、oct、hex、setbase
  • 浮点数的精度(precision , setprecision)
  • 设置域宽(setw,width)
  • 用户自定义的流操作纵算字

2.1、流操纵算子dec、oct、hex、setbas

在C++中,流操纵算子dec、oct、hex和setbase用于设置输出整数时的进制格式。它们的作用如下:

  • dec:将输出的整数视为十进制形式。这是默认的进制格式。
std::cout << std::dec << 123; // 输出 123
  • oct:将输出的整数视为八进制形式。
std::cout << std::oct << 123; // 输出 173 (八进制)
  • hex:将输出的整数视为十六进制形式。
std::cout << std::hex << 123; // 输出 7b (十六进制)
  • setbase:设置输出整数的基数(进制)。它可以接受一个整数参数,表示要使用的进制数。
std::cout << std::setbase(16) << 123; // 输出 7b (十六进制)

这些流操纵算子影响之后的整数输出操作,使得整数以特定的进制格式进行输出。
使用流操纵算子需要:#include<iomainp>
请添加图片描述
请添加图片描述

2.2、设置域宽的流操纵算子

  • 设置域宽(setw,width)
  • 两者功能相同,一个是成员函数,一个是流操作算子,调用方式不同。
cin >> setw(4); 或者cin.width(5)
cout << setw(4); 或者cout.width(5) 

在C++中,setw<iomanip> 头文件中定义的一个函数,用于设置输出流中下一个字段的宽度。这个函数的原型如下:

#include <iomanip>

std::setw(int n);

其中,n 是一个整数,表示要设置的字段宽度。

通常,setwstd::cout 一起使用,用于格式化输出。例如,你可以使用 setw 来设置输出的字段宽度,以便让输出更加整齐。下面是一个简单的示例:

#include <iostream>
#include <iomanip>
using namespace std;

int main() {
     // 方式一
     cout <<  setw(7) << "Hello" <<  endl;
     cout <<  setw(3) << "World" <<  endl;
	 
	 // 方式二
     cout.width(7);
     cout << "Hello" <<  endl;
     cout.width(2);
     cout  << "World" <<  endl;
	 
	 // 方式三
     cout << cout.width(5) << "Hello" <<  endl;
     cout  << cout.width(2) << "World" <<  endl;

    return 0;
}
// OUT
beida2 % g++ 5.cpp -o 5
beida2 % ./5           
  Hello
World
  Hello
World
    0Hello
 0World
beida2 % 

在这个示例中,setw(10) 设置了输出字段的宽度为 10 个字符。因此,无论单词是多长,输出都会在 10 个字符的宽度内显示。如果单词长度小于 10,会在单词前补足空格;如果大于 10,会按照单词长度输出。

例二:

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    int w = 4;
    char string[10];
    cin.width(5); // 为什么只能读到1234四个字符呢!因为第五个字符默认是/0。
    // 宽度设置是一次性的,每次读入和输出之间都要设置宽度
    while(cin >> string)
    {
        cout.width(w++);
        cout << string << endl;
        cin.width(5);
    }
}
//OUT
beida2 % ./6
1234567890
1234
 5678
    90

例题三:

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    int n = 141;
    double x = 123456.89 , y = 12.34567;

    // 1) 分别以十六进制、十进制、八进制先后输出n
    cout << "1) " << hex << n << " " << dec << n << " " << oct << n << endl;
    // 2) 保留5位有效数字
    cout << "2) " << setprecision(5) << x << " " << y << " " << endl;
    // 3) 保留小数点后5位
    cout << "3) " << fixed << setprecision(5) << x << " " << y << endl;
    // 4) 科学技术法输出,且保留小数点后面5位
    cout << "4) " << scientific << setprecision(5) << x << " " << y << endl;
    // 5) 非负数要显示正号,输出宽度为12字符,宽度不足则用'*'填充
    cout << "5) " << showpos << fixed << setw(12) << setfill('*') << 12.1 << endl;
    // 6) 非负数不显示正号,输出宽度为12字符,宽度不足则右边用填充字符填充
    cout << "6) " << noshowpos << setw(12) << left << 12.1 << endl;  // left:左对齐
    // 7) 输出宽度为12字符,宽度不足则左边用填充字符填充
    cout << "7) " << setw(12) << right << 12.1 << endl;
    // 8) 宽度不足时,负号和数值分列左右,中间用填充字符填充
    cout << "8) " << setw(12) << internal << -12.1 << endl;
    cout << "9) " << 12.1 << endl;
 }
// OUT
1) 8d 141 215
2) 1.2346e+05 12.346 
3) 123456.89000 12.34567
4) 1.23457e+05 1.23457e+01
5) ***+12.10000
6) 12.10000****
7) ****12.10000
8) -***12.10000
9) 12.10000 

2.3、用户自定义流操作算子

ostream $tab(ostream &output)
{
	return output << '\t';
}
cout << "aa" << tab << "bb" << endl; // aa和bb之间相隔一个制表符输出

输出:aa		bb

上面为何能这样输出呢!
因为iostream里对 << 进行了重载(成员函数)

ostream & operator<<(ostream & (*p)(ostream &));

该函数内部会调用p所指向的函数,且以*this作为参数。hex、dex、oct函数。

3、文件读写

3.1、文件和流

在这里插入图片描述
在C++中,这些类都是用于文件输入输出的类。它们分别具有以下作用:

  1. ifstream: 这个类用于从文件中读取数据。ifstream 类派生自 istream,因此可以使用输入运算符 (>>) 和其他与输入相关的方法来从文件中读取数据。

  2. iostream: 这个类可以用于同时进行输入和输出操作,但通常它用于控制台输入输出。iostream 类派生自 istreamostream,所以它继承了输入和输出的功能。

  3. ofstream: 这个类用于向文件中写入数据。ofstream 类派生自 ostream,因此可以使用输出运算符 (<<) 和其他与输出相关的方法来向文件中写入数据。

  4. fstream: 这个类可以用于同时进行文件输入和输出操作,即读写文件。fstream 类同时派生自 ifstreamofstream,因此可以实现文件的读写操作。

这些类提供了方便的方法来处理文件输入输出,在C++中常用于文件的读取、写入以及读写组合操作。

3.2、创建文件

请添加图片描述
请添加图片描述
请添加图片描述

3.3、文件的读写

请添加图片描述

3.4、文件的读写指针

请添加图片描述请添加图片描述

3.4、字符文件读写

请添加图片描述

#include<iostream>
#include<fstream>
#include<vector>
#include<algorithm>
using namespace std;

int main()
{
    vector<int> v;
    ifstream srcFile("in.txt", ios::in);  // 打开文件,并对文件进行读取;采用ios::in方式打开文件
    ofstream destFile("out.txt", ios::out);  // 打开文件,并进行写入,用ios::out
    int x;
    while(srcFile >> x)  // srcFile内容输入到x;
    {
        v.push_back(x);
    }
    sort(v.begin(), v.end());  
    for (int i=0; i < v.size(); i++)
    {
        destFile << v[i] << " ";  // 表示v[i]输出到destFile
    }
    // 打开的文件必须要关闭;
    // 1、文件是在硬盘上的:而硬盘的访问速度要比内存慢很多的。
    // 要往文件写入数据时候,需要先把内存填满,再访问硬盘,把数据整体写入硬盘文件中。如果不关闭文件,
    // 可能数据还在内存中,导致文件中的数据缺失等问题。
    // 2、在许多操作系统中,一个应用程序所打开的文件数目是有上限的,如果不停的打开文件忘了关的话,
    // 超过上限,这个程序就会打不开任何文件了;
    destFile.close();  
    srcFile.close();

    return 0;
}

注意事项:打开的文件必须要关闭:

  • 1、文件是在硬盘上的:而硬盘的访问速度要比内存慢很多的。要往文件写入数据时候,需要先把内存填满,再访问硬盘,把数据整体写入硬盘文件中。如果不关闭文件,可能数据还在内存中,导致文件中的数据缺失等问题。(尽量减少访问硬盘的次数)。
  • 2、在许多操作系统中,一个应用程序所打开的文件数目是有上限的,如果不停的打开文件忘了关的话,超过上限,这个程序就会打不开任何文件了;

3.5、二进制文件读写(非常重要)

请添加图片描述

例题1:二进制文件的写入与读取

#include<iostream>
#include<fstream>
using namespace std;

int main()
{
    // ofstream创建some.dat二进制文件,并以写ios::out和二进制方式打开
    ofstream fout("some.dat", ios::out | ios::binary);
    int x = 120;
    // write的第一个参数:const char *,x为int,所以用了强制类型转化
    // write的第二个参数:表示写入数据内存的大小,这里是4个字节
    fout.write((const char *)(&x), sizeof(int));
    fout.close();
    // 以读ios::in和二进制方式打开
    ifstream fin("some.dat", ios::in | ios::binary);
    int y;
    // read第一个参数类型为char *指针
    fin.read((char *) &y, sizeof(int));
    fin.close();
    cout << y << endl;
    return 0;
}

请添加图片描述

例题2:文件的写入

从键盘输入几个学生的姓名的成绩,并以二进制文件形式保存。

#include<iostream>
#include<fstream>
using namespace std;

struct Student
{
    char name[20];
    int score;
};

int main()
{
    Student s;
    ofstream OutFile("student.dat", ios::out | ios::binary);
    while(cin >> s.name >> s.score)
    {
        OutFile.write((char *) & s, sizeof(s));
    }
    OutFile.close();
    return 0;
}

请添加图片描述

例题3:二进制文件的写入

C 库函数 int strncmp(const char *str1, const char *str2, size_t n) 把 str1 和 str2 进行比较,最多比较前 n 个字符。

// 声明
int strncmp(const char *str1, const char *str2, size_t n)

参数

  • str1 – 要进行比较的第一个字符串。
  • str2 – 要进行比较的第二个字符串。
  • n – 要比较的最大字符数。

返回值:

  • 如果返回值 < 0,则表示 str1 小于 str2。
  • 如果返回值 > 0,则表示 str1 大于 str2。
  • 如果返回值 = 0,则表示 str1 等于 str2。
#include<iostream>
#include<fstream>
using namespace std;

struct Student
{
    char name[20];
    char end[20];
    int score;
};

int main()
{
    Student s;
    ofstream OutFile("student.dat", ios::out | ios::binary);
    int i = 0;
    int over;
    while(cin >> s.name >> s.score >> s.end)
    {
        OutFile.write((char *) & s, sizeof(s));
        over = strncmp(s.name, s.end, 4);
        if (over == 0)
        {
            break;
        }

    }
    OutFile.close();
    return 0;
}

输出结果:(首先会生成一个二进制student.dat文件)
beida2 % ./10
Tom 10 end
ZBS 27 end
end 0 end
beida2 %

例题4:二进制文件内容的读取

#include<iostream>
#include<fstream>
using namespace std;

struct Student
{
    char name[20];
    char end[20];
    int score;
};

int main()
{
    Student s;
    ifstream inFile("student.dat", ios::out | ios::binary);
    // 查看文件是否打开重开
    if(!inFile)
    {
        cout << "error" << endl;
        return 0;
    }
    // 将文件内容读取到s这个地址上,
    while(inFile.read((char* ) & s, sizeof(s)))
    {
        int readedBytes = inFile.gcount();  // 看看刚才读取了多少字节
        cout << s.name << " " << s.score << " " << s.end << endl;
        cout << "readedBytes = " << readedBytes << endl;
    }

    inFile.close();
    return 0;
}

OUT:
beida2 % ./11
Tom 10 end
readedBytes = 44
zbs 27 end
readedBytes = 44
end 0 end
readedBytes = 44
beida2 %

例题5:二进制文件内容的读写:并更改内容

student.dat文件内容如下:
Tom 10 end
zbs 27 end
end 0 end

#include<iostream>
#include<fstream>
using namespace std;

struct Student
{
    char name[20];
    char end[20];
    int score;
};

int main()
{
    Student s;
    fstream iofile("student.dat", ios::in | ios::out | ios::binary);
    if(!iofile)
    {
        cout << "error" << endl;
        return 0;
    }
    iofile.seekp(sizeof(s), ios::beg);  // 定位写指针到第2个记录
    iofile.write("Mike", strlen("Mike")+1);  // +1是表示把结尾的\0也写入
    iofile.seekg(0, ios::beg);  // 定位读指针到开头
    while(iofile.read((char*) & s, sizeof(s)))
    {
        cout << s.name << " " << s.score << " " << s.end << endl;
    }
    iofile.close();
    return 0;
}

输出:
Tom 10 end
Mike 27 end (这里ZBS变成了Mike
end 0 end

例题5:二进制文件内容的读写:并更改内容

#include<iostream>
#include<fstream>
using namespace std;
/*
用法示例:./13 src.dat dest.dat
即将src.dat拷贝到dest.dat
如果dest.dat原来就有,则原来的文件会被覆盖
*/
// argc:命令行参数的个数;argv:命令行参数;
int main(int argc, char * argv[])
{
    if(argc != 3)
    {
        cout << "File name missing!" << endl;
        return 0;
    }
    ifstream inFile(argv[1], ios::binary | ios::in); // 打开文件用于读取
    if (!inFile)
    {
        cout << "Source file open error." << endl;
        return 0;
    }
    ofstream outFile(argv[2], ios::binary | ios::out); // 打开文件用于写
    if(!outFile)
    {
        cout << "New file open error." << endl;
        inFile.close();  // 打开的文件一定要关闭

        return 0;
    }
    char c;
    while(inFile.get(c))  // 每次读取一个字符
    {
        outFile.put(c); // 每次写入一个字符
    }
    outFile.close();
    inFile.close();
    return 0;

}

请添加图片描述

4、二进制文件和文本文件的区别请添加图片描述请添加图片描述

  • 18
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

☞源仔

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

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

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

打赏作者

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

抵扣说明:

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

余额充值