突破编程_C++_基础教程(输入、输出与文件)

1 流和缓冲区

C++中,流( stream )和缓冲区( buffer )是两个紧密相关的概念,它们在处理输入和输出时起着重要的作用。
流( Stream )
流是一种抽象的概念,用于表示数据的流动。在 C++ 中,流是一个类对象,它封装了与输入/输出设备(如键盘、显示器、文件等)的交互。C++标准库提供了多种流类,如 std::cin (标准输入流,通常用于从键盘读取数据)、 std::cout (标准输出流,通常用于向显示器输出数据)以及 std::fstream (文件流,用于文件的读写)。
缓冲区( Buffer )
缓冲区是一个用于临时存储数据的内存区域。当使用流进行输入或输出时,数据通常不会直接发送到设备或从设备读取,而是首先存储在缓冲区中。缓冲区的使用可以提高输入/输出的效率,因为它允许程序以块的形式处理数据,而不是一个字符一个字符地处理。
对于输出流,程序首先将数据写入缓冲区,然后当缓冲区满或程序显式地刷新缓冲区时,数据才会被发送到输出设备。对于输入流,程序从缓冲区读取数据,当缓冲区为空时,流会从输入设备读取更多的数据填充缓冲区。
缓冲区的类型
C++中的流缓冲区主要有三种类型:
(1)全缓冲:当缓冲区满时,缓冲区的数据才会被发送。这种方式适用于大量数据的输入/输出。
(2)行缓冲:当遇到换行符时,缓冲区的数据才会被发送。这种方式通常用于终端设备,如键盘和显示器。
(3)不带缓冲:数据立即被发送,不经过缓冲区。这种方式通常用于错误报告和紧急情况。
iostream 文件
C++ 中的 iostream 文件中包含了一些用来实现管理流和缓冲区的类。iostream 中的 io 是 “input/output” 的缩写,而 stream 表示流,即数据流动的通道。
在 iostream 库中,主要包含了以下几个组件:
(1)缓冲区( streambuf 类): streambuf 类为流提供了一个缓冲区,这个缓冲区用于暂存输入/输出的数据,从而允许更高效的I/O操作。streambuf 是一个抽象基类,不能直接实例化。它的实现(如 filebuf 、 stringbuf 等)会作为其他流类(如 ifstream 、 ofstream 、 istringstream 、 ostringstream 等)的成员被使用。
(2)输入流( istream 类):用于从输入设备(如键盘)读取数据。
(3)输出流( ostream 类):用于向输出设备(如显示器)写入数据。
(4)输入输出流 ( iostream 类):从 istream 和 ostream 继承了输入和输出的方法(多重继承),既可以读取数据也可以写入数据。
(5)流对象:如 std::cin(标准输入流,通常用于从键盘读取数据)、std::cout(标准输出流,通常用于向显示器输出数据)、std::cerr(用于输出错误消息)和 std::clog(用于输出日志消息)。
(6)操纵器( manipulators):这些是用来控制流的状态或格式的函数,例如 std::endl、std::flush、std::setprecision 等。
(7)流提取运算符( >> ):用于从输入流中读取数据。
(8)流插入运算符( << ):用于向输出流中写入数据。
重定向
可以使用 C 标准库函数 freopen 函数来重定向标准输入流 std::cin 或标准输出流 std::cout 到文件或其他流。当重定向一个流时,实际上是改变了流与底层设备(如文件或控制台)的关联。例如,可以将 std::cin 重定向到一个文件,这样当从 std::cin 读取时,实际上是从文件中读取数据。如下为样例代码:

#include   
#include   

using namespace std;

int main() {
	// 重定向 std::cin 到一个输入文件:在这个文件中输入 1 ,并保存
	freopen("input.txt", "r", stdin);

	// 重定向 std::cout 到一个输出文件  
	freopen("output.txt", "w", stdout);

	int val;
	cout << "get a number from input.txt : "; // cout 写入到 output.txt 
	cin >> val; // 实际上从 input.txt 读取  

	cout << val << endl; // cout 写入到 output.txt  

	// 关闭重定向  
	//freopen(NULL, "r", stdin);  // 重置 std::cin 到标准输入  
	//freopen(NULL, "w", stdout); // 重置 std::cout 到标准输出  

	return 0;
}

执行完这段代码后,output.txt 被存入字符串:“get a number from input.txt : 1” 。
在上面代码中,freopen 函数将 std::cin 重定向到名为 input.txt 的文件用于读取,将 std::cout 重定向到名为 output.txt 的文件用于写入。因此,当从 std::cin 读取时,实际上是在读取 input.txt 文件的内容,而当向 std::cout 写入时,数据会被写入到 output.txt 文件中。
注意,重定向流可能会影响程序的其他部分,因为它改变了流的默认行为。因此,在重定向流之后,务必小心处理输入和输出,以避免意外的行为或错误。
另外,如果想要在执行完一些重定向操作之后恢复流的原始状态,可以使用 freopen 函数将流重新关联到其原始设备(比如将第一个参数设置为 NULL 即可将输入与输出定向为标准输入与输出)。

2 输出

输出流( output stream )是 I/O 流库中的一个关键概念,它允许程序将数据发送到特定的目的地,如控制台、文件或其他类型的设备。C++ 标准库中的 ostream 类是输出流的基础。

2.1 std::cout 的基本使用

std::cout 是 C++ 中最常用的输出流对象,它通常与控制台(或终端)相关联,用于向用户显示信息。可以使用插入运算符 << 向 std::cout 发送数据,该运算符将数据发送到输出流中。
。如下为样例代码:

#include   
#include   

using namespace std;

int main() {

	int val1 = 1;
	double val2 = 1.21;
	char val3 = 'a';
	string val4 = "hello";

	std::cout << "int val1 = " << val1 << std::endl;
	std::cout << "double val2 = " << val2 << std::endl;
	std::cout << "char val3 = " << val3 << std::endl;
	std::cout << "string val4 = " << val4 << std::endl;

	return 0;
}

上面代码的输出为:

int val1 = 1
double val2 = 1.21
char val3 = a
string val4 = hello

除了 std::cout ,C++ 标准库还提供了其他一些输出流对象,例如:
std::cerr:用于输出错误消息。与 std::cout 不同的是,std::cerr 通常不会被缓冲,因此它用于需要立即显示的错误或诊断信息。
std::clog:用于输出日志消息。与 std::cerr 类似,但它通常会被缓冲。
std::wcout:与 std::cout 类似,但用于输出宽字符(wchar_t 类型)。
std::wcerr 和 std::wclog:与 std::cerr 和 std::clog 类似,但用于输出宽字符。
此外,还可以创建自定义的输出流,通过继承 std::ostream 类并重载插入运算符来实现。这允许控制数据如何被发送到特定的目的地,例如写入到特定的文件或进行特定的格式化处理。
在处理输出流时,还可以使用操纵器( manipulators )来更改流的状态或格式。例如,std::endl 操纵器不仅插入一个新行,还刷新输出缓冲区,确保所有数据都被发送到其目标。 std::flush 操纵器也可以用来刷新输出缓冲区。

2.2 std::cout 的格式化

std::cout 可以使用各种格式化标志和操纵器(manipulators)来控制输出的格式。以下是一些常见的格式化选项:
(1)整数输出
(1.1)十进制

int val = 12;
std::cout << val << std::endl;

// 输出 12

(1.2)十六进制

int val = 12;
std::cout << std::hex << val << std::endl;

// 输出 c

(1.3)八进制

int val = 12;
std::cout << std::oct << val << std::endl;

// 输出 14

(1.4)二进制
std::cout 默认不直接支持二进制数据的输出。如果想要输出一个整数的二进制表示,则要手动将整数转换为二进制字符串,然后再使用 std::cout 输出这个字符串。

#include   
#include   

void printBinary(int val) {
    // std::bitset 可以将整数转换为二进制字符串  
    std::bitset<32> binaryVal(val); // 4 个字节,共 32 位  
    // 输出二进制字符串  
    std::cout << binaryVal << std::endl;
}

int main() {
    int val = 12;
    printBinary(val); // 输出 num 的二进制表示  
    return 0;
}

上面代码输出为:

00000000000000000000000000001100

(2)浮点数输出
(2.1)固定点表示法
默认情况下,std::cout 使用科学记数法来输出非常大或非常小的浮点数。使用 std::fixed 使浮点数总是以固定的小数位数显示。

double pi = 3.141592653589793;

// 不使用 std::fixed,默认输出可能使用科学记数法  
std::cout << "default output: " << pi << std::endl;

// 使用 std::fixed 以小数位数显示  
std::cout << std::fixed << "fixed output: " << pi << std::endl;

(2.2)固定点表示法
默认情况下,std::cout 使用科学记数法来输出非常大或非常小的浮点数。使用 std::fixed 使浮点数总是以固定的小数位数显示。

double pi = 3.141592653589793;
std::cout << std::scientific << pi << std::endl;

// 输出 3.141593e+00

(2.2)设置精度
设置精度使用接口 std::setprecision ,注意该接口需要引用头文件: #include

double pi = 3.141592653589793;
std::cout << std::setprecision(3) << pi << std::endl;

// 输出 3.14

(3)字符串输出

std::cout << "Hello, World!" << std::endl;

// 输出 Hello, World!

std::string str = "Hello, World!";  
std::cout << str << std::endl;

// 输出 Hello, World!

(4)布尔值输出
布尔值默认以整数形式显示( true为 1 , false 为 0 )。可以通过 std::boolalpha 来改变这一点,使 true 和 false 以文字形式显示。

bool flag = true;  
std::cout << flag << std::endl; 

// 输出 1

std::cout << std::boolalpha << flag << std::endl;

// 输出 true

(5)设置填充和宽度
使用 std::setw 操纵器可以设置下一个输出字段的宽度。如果输出数据小于这个宽度,std::cout 会在数据前面或后面填充空格,直到达到指定的宽度。此外还可以使用 std::setfill 操纵器来设置填充字符。

int val = 123;
std::cout << std::setw(5) << std::setfill('0') << val << std::endl;  

// 输出 00123

(6)设置左对齐和右对齐
使用std::left或std::right来控制输出的对齐方式。

#include   
#include 

int main() {
    std::cout << std::left << std::setw(10) << "Hello" << std::endl; // 左对齐  
    std::cout << std::right << std::setw(10) << "Hello" << std::endl; // 右对齐

    return 0;
} 

上面代码输出为:

Hello
     Hello

2.3 多线程中应用 std::cout

C++ 中,std::cout 并不是线程安全的。这意味着如果从多个线程同时写入 std::cout,可能会遇到数据竞争(data race)的问题,这会导致未定义的行为(程序可能会崩溃或者无响应)。
可以使用互斥锁(如 std::mutex )来保护对 std::cout 的访问。每个线程在写入 std::cout 之前必须获取锁,并在写入完成后释放锁。这样可以确保每次只有一个线程可以访问 std::cout。如下为样例代码:

#include   
#include   
#include   

std::mutex g_coutMutex;  // 全局互斥锁  

void safePrint(const std::string& message) {
    std::lock_guard<std::mutex> lock(g_coutMutex);	// std::lock_guard是一个方便的RAII包装器,它会在构造时锁定互斥锁,并在析构时解锁。这使得代码更加简洁,并减少了出错的可能性。
    std::cout << message << std::endl;
}

int main() {
    std::thread t1(safePrint, "Hello from thread 1");
    std::thread t2(safePrint, "Hello from thread 2");

    t1.join();
    t2.join();

    return 0;
}

2.4 printf

printf 是 C 语言标准库中的一个函数,用于格式化输出到标准输出流(通常是终端或控制台窗口)。它接受一个格式字符串和与之对应的值作为参数,然后根据格式字符串中的说明符将值打印出来。 printf 函数是线程安全的,所以在 C++ 的多线程开发中,比 std::cout 用的更方便一些。
** printf 常用的格式说明符及其含义:**
%d 或 %i:带符号十进制整数。
%u:无符号十进制整数。
%f:浮点数(默认保留小数点后六位)。
%c:字符。
%s:字符串。
%p:指针地址。
%x 或 %X:无符号十六进制整数(小写或大写)。
%o:无符号八进制整数。
%%:输出一个 % 字符。
除了转换说明符,格式字符串还可以包含以下可选的标志、宽度、精度和长度修饰符:
标志:
-:左对齐输出。
+:在正数前面显示符号。
(空格):在正数前面显示空格。
#:对于 f、e、E、g、G,输出小数点;对于 o,输出前导零;对于 x 或 X,输出 0x 或 0X 前缀。
0:用零填充空白处。
精度:
对于整数(d、i、o、u、x、X),指定最小数字个数。
对于浮点数(e、E、f、g、G),指定小数点后的数字个数。
对于字符串(s),指定最大字符数。
长度修饰符:
h:指定短整型(short)或单字符(char)。
l:指定长整型(long)。
ll:指定长长整型(long long)。
L:指定宽字符或宽字符串。
j:指定 intmax_t 类型。
z:指定 size_t 类型。
t:指定 ptrdiff_t 类型。

3 输入

C++ 的输入流(Input Stream)通常指的是从某个数据源(如键盘、文件等)读取数据的流。C++标准库中的 头文件提供了基本的输入流功能,主要通过 std::istream 类及其派生类 std::cin 来实现。
std::cin是预定义的对象,代表从标准输入(通常是键盘)接收数据的输入流。可以使用 >> 运算符从std::cin读取数据,这些数据会被存储在相应的变量中。
如下为样例代码(使用 std::cin 从键盘读取数据):

#include   
  
int main() {  
    int val;  
    std::cout << "input an integer : ";  
    std::cin >> val; 	// 从标准输入读取整数并存储在变量 val 中  
    std::cout << "the input integer is: " << val << std::endl;  
  
    return 0;  
}

4 文件操作

C++ 的文件操作主要涉及文件的打开、关闭、读取和写入。C++标准库中的 头文件提供了用于文件操作的类,其中 std::ifstream 用于读取文件, std::ofstream 用于写入文件,而 std::fstream 则既可以读取也可以写入文件。
打开文件
在读取或写入文件之前,需要使用相应的文件流对象打开一个文件。可以通过提供文件名来构造一个文件流对象,该对象会在构造时尝试打开文件。如下为样例代码:

#include   
#include   

int main() {
    // 创建一个用于写入的文件流对象  
    std::ofstream outfile("test.txt");

    // 检查文件是否成功打开  
    if (!outfile) {
        std::cerr << "failed to open the file" << std::endl;
        return 1;
    }

    // 写入一些数据到文件  
    outfile << "hello" << std::endl;

    // 关闭文件  
    outfile.close();

    return 0;
}

读取文件
使用 std::ifstream 可以读取文件的内容。可以使用流提取运算符 >> 或 getline 函数来读取数据。如下为样例代码:

#include   
#include   
#include  

int main() {
    // 创建一个用于读取的文件流对象  
    std::ifstream infile("test.txt");

    // 检查文件是否成功打开  
    if (!infile) 
	{
        std::cerr << "failed to open the file" << std::endl;
        return 1;
    }

    // 读取文件内容  
    std::string line;
    while (std::getline(infile, line)) 
	{
        std::cout << line << std::endl;
    }

    // 关闭文件  
    infile.close();

    return 0;
}

快速读取大文件
如果需要快速读取一个大文件,则要避免逐行读取或者逐字符读取带来的额外开销。一种快速读取文件的方法是使用文件流的 read 成员函数,它可以一次读取多个字符,这样可以减少系统调用的次数,从而提高读取效率。如下为样例代码:

#include   
#include   
#include   

int main() {
    // 打开文件  
    std::ifstream file("large_file.bin", std::ios::binary);

    // 检查文件是否成功打开  
    if (!file)
	{
        std::cerr << "failed to open the file" << std::endl;
        return 1;
    }

    // 获取文件大小  
    file.seekg(0, std::ios::end);
    std::streamsize fileSize = file.tellg();
    file.seekg(0, std::ios::beg);

    // 分配足够的内存来存储文件内容  
    std::vector<char> buffer(fileSize);

    // 读取文件内容到buffer  
    if (!file.read(buffer.data(), fileSize)) 
    {
        std::cerr << "读取文件失败" << std::endl;
        return 1;
    }

    // 在这里处理文件内容,例如可以将其输出到标准输出  
    std::cout.write(buffer.data(), fileSize);

    // 关闭文件  
    file.close();

    return 0;
}

读写文件
下面是一个同时包含读取和写入操作的示例:

#include   
#include   
#include   

int main() {
    // 写入文件  
    std::ofstream outfile("test.txt");
    if (!outfile)
    {
        std::cerr << "failed to open the file" << std::endl;
        return 1;
    }
    outfile << "first line" << std::endl;   //写入第一行
    outfile << "second line" << std::endl;  //写入第二行
    outfile.close();

    // 读取文件  
    std::ifstream infile("test.txt");
    if (!infile) {
        std::cerr << "failed to open the file" << std::endl;
        return 1;
    }
    std::string line;
    while (std::getline(infile, line))
    {
        std::cout << line << std::endl;
    }
    infile.close();

    return 0;
}

文件流的状态
可以使用文件流对象的 is_open() 成员函数来检查文件是否已成功打开。此外,还可以使用 fail() , eof() , 和 bad() 等成员函数来检查流的状态。
文件流的其他操作
clear(): 重置流的状态标志。
seekg() 和 seekp(): 移动文件的读写指针。
tellg() 和 tellp(): 返回文件的读写指针当前位置。
flush(): 清空输出缓冲区,确保所有数据都被写入文件。

你可能感兴趣的:(突破编程_C++_基础教程,c++,开发语言)