C++ 文件操作篇

news2025/1/19 20:26:55

C++ 文件操作篇

文章目录

  • C++ 文件操作篇
  • 1 简介
    • 1.1 继承关系
    • 1.2 流
    • 1.3 缓冲区
      • 输入输出流中的缓冲streambuf
  • 2 文件操作步骤
    • 2.1 头文件
    • 2.2 创建流对象
    • 2.3 打开文件
    • 2.4 读取数据
      • 第一种:**按元素直接读**
      • 第二种:**使用getline按行读**
      • 第三种:**使用*get***
    • 2.5 写入数据
    • 2.6 fstream类
    • 2.7 关闭文件
  • 3 数据类型转换
  • 4 状态标志符的验证
  • 5 获得和设置流指针
  • 6 二进制文件读写
    • 6.1 写文件
    • 6.2 读文件
  • 7 缓存和同步
  • 参考文档:

1 简介

1.1 继承关系

请添加图片描述

这里文件操作主要有:

ios->ostream->ofstream
ios->istream->ifstream
iostream->fstream
(还继承了两个缓冲区,一个用于输入,一个用于输出)
(此类用于文件I/O的同步,即协调地移动输入缓冲区的输入指针和输出缓冲区的输出指针)
(输入输出模式时使用fstream类)

1.2 流

在程序设计中,数据输入/输出(I/O)操作是必不可少的,C++语言的数据输入/输出操作是通过I/O流库来实现的。
C++中把数据之间的传输操作称为流,

**输出流:**数据从内存传送到某个载体或设备中
**输入流:**某个载体或设备传送到内存缓冲区变量中

  • 标准I/O流:内存与标准输入输出设备之间信息的传递;
  • 文件I/O流:内存与外部文件之间信息的传递;
  • 字符串I/O流:内存变量与表示字符串流的字符数组之间信息的传递
流类分类流类名称流 类 作 用
流基类ios所有流类的父类,保存流的状态并处理错误
输入流类istream输入流基类,将流缓冲区中的数据作格式化和非格式化之间的转换并输入 ifstream
ifstream文件输入流类
stream_withassigncin输入流类,即操作符>>输入流
istrstream串输入流类, 基于C类型字符串char*编写
istringstream串输入流类, 基于std::string编写
输出流类ostream输出流基类,将流缓冲区中的数据作格式化和非格式化之间的转换。并输出
ofstream文件输出流类
ostream_withassignCout、cerr、clog的输出流类,即操作符<<输出流
ostrstream串输入流类, 基于C类型字符串char*编写
ostringstream串输入流类, 基于std::string编写
输入/输出流类iostream多目的输入/输出流类的基类
fstream文件流输入/输出类
strstream串流输入/输出类, 基于C类型字符串char*编写
stringstream串流输入/输出类, 基于std::string编写

1.3 缓冲区

文件输出输入使用缓冲区,在声明每个ofsream or ifsream对象时程序会为其自动分配该对象自己的缓冲区;分为输入缓冲区输出缓冲区

缓冲区就是一块存储空间,它是为了匹配程序处理速度和外设处理速度;比如程序一次处理1byte,但是磁盘一次读取512bytes;又或者程序一次处理1byte,可以1byte地从磁盘读取,但是由于硬件读取一次数据复杂且操作慢,因此使用缓冲区可以加快程序处理速度。

何时清空缓存: 1.缓存区满时 2.使用文件close()方法时,为了保证关闭文件时文件被更新。

输入输出流中的缓冲streambuf

  • stl(标准库)提供了缓冲区类streambuf,提供给输入输出流使用,每个标准的输入输出流对象均包含一个streambuf指针
  • 可以通过调用rdbuf()获取该指针,从而直接访问底层streambuf对象
  • streambuf最精彩的部分在于它支持<<,>>操作,以及迭代器操作,支持自定义。

2 文件操作步骤

2.1 头文件

#include <fstream>

2.2 创建流对象

ofstream fout("xxx.txt");        //文件写操作 内存写入存储设备 
ifstream fin("xxx.txt");       //文件读操作,存储设备读区到内存中
fstream  foi("xxx.txt");        //读写操作,对打开的文件可进行读写操作 

2.3 打开文件

在从文件读取信息或者向文件写入信息之前,必须先打开文件。ofstreamfstream 对象都可以用来打开文件进行写操作,如果只需要打开文件进行读操作,则使用 ifstream 对象。

void open(const char *filename, ios::openmode mode);  // filename : 文件路径  openmode : 打开方式
模式标志描述
ios::app追加模式。所有写入都追加到文件末尾。
ios::ate文件打开后定位到文件末尾。
ios::in打开文件用于读取。
ios::out打开文件用于写入。
ios::trunc如果该文件已经存在,其内容将在打开文件之前被截断,即把文件长度设为 0。

例如,要以写入模式打开文件,并希望截断文件,以防文件已存在

ofstream fout;
fout.open("file.dat", ios::out | ios::trunc );

如果想要打开一个文件用于读写

ifstream  afile;
afile.open("file.dat", ios::out | ios::in );

*** 可以不显式的调用open()函数 ***

ofstream out("xxx.txt", ios::out);
ifstream in("xxx.txt", ios::in);
fstream foi("xxx.txt", ios::in|ios::out);
/* 直接调用了其默认的打开方式,因为在stream类的构造函数中调用了open()函数,并拥有同样的构造函数,所以在这里可以直接使用流对象进行文件的操作 */

成员函数is_open()可以对文件是否打开进行验证

2.4 读取数据

① 为了保持健壮性,读文件要有验证代码

ifstream fin("xxx.txt", ios::in);

if(!fin.is_open())
{
    std::cerr<<"cannot open the file"
}
//或者
if(!fin)
 {
    std::cerr<<"cannot open the file";
}  

② 读取文件

第一种:按元素直接读

	string tmp;
	ifstream fin("writein.txt");

	if (!fin){
		cerr << "Fail!\n";
	}
	else{
		// 这种方法会从fin中逐个单词读取(也就是遇到空格、换行时会停止)
		while (fin >> tmp)
		{
			cout << tmp << endl;
		}
		fin.close();
	}

第二种:使用getline按行读

输入流成员函数getline()用法

getline是C++标准库函数;它有两种形式,一种是头文件< istream >中输入流成员函数;一种在头文件< string >中普通函数;

语法结构:

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

istream中读取至多n个字符(包含结束标记符)保存在s对应的数组中。即使还没读够n个字符,如果遇到delim 字数达到限制,则读取终止delim都不会被保存进s对应的数组中。

  • 读入了文件结束标志 EOF
  • 读到一个新行
  • 达到字符串的最大长度

–如果getline没有读入字符,将返回false,可用于判断文件是否结束

	char buf[1021] = { 0 };
	string tmp;
	ifstream fin("writein.txt");

	if (!fin){
		cerr << "Fail!\n";
	}
	else{
        // char *
		while (fin.getline(buf, sizeof(buf)))
		{
			cout << buf << endl;
		}
        // string	
        while (getline(fin, tmp))
		{
			cout << tmp << endl;
		}
		fin.close();
	}

第三种:使用get

	// (不推荐,效率)
	char c;
	ifstream fin("writein.txt");

	if (!fin){
		cerr << "Fail!\n";
	}
	else{
		while (c = fin.get() != EOF)
		{
			cout << c;
		}
		fin.close();
	}

2.5 写入数据

	string name;
	ofstream outfile("writein.txt", ios::app); // 以追加模式开启文件,新数据会被加到文件尾端

	cout << "Writing to the file" << endl;
	cout << "Enter your name: ";
	cin >> name;
	outfile << name << endl;

2.6 fstream类

#include<fstream>
using namespace std;
int main() {
	fstream file;
	file.open("1.txt",ios::out); //以只写模式打开文件
	char buf[] = "test";
	file.write(buf,sizeof(buf));//写入文件
	file.close(); //关闭文件

	file.open("1.txt", ios::app); //以追加模式打开文件
	char buf1[] = "test1";
	file.write(buf1, sizeof(buf));//写入文件末尾
	file.close(); //关闭文件

	file.open("1.txt",ios::in); //以只读方式打开文件
	char buf2[0xFF]; //存储读取的内容
	file.read(buf2,0xFF); //读文件
	file.close(); //关闭文件
}

2.7 关闭文件

当文件读写操作完成之后,我们必须将文件关闭以使文件重新变为可访问的。成员函数close()它负责将缓存中的数据排放出来并关闭文件。这个函数一旦被调用,原先的流对象就可以被用来打开其它的文件了,这个文件也就可以重新被其它的进程所访问了。为防止流对象被销毁时还联系着打开的文件,析构函数将会自动调用关闭函数close

3 数据类型转换

一般默认从文件中读取的是字符格式或者字符串格式的数据,如果是数字要转化为float等格式怎么办呢?

直接定义负责接受的变量数据类型,按行分后再按单词分

  • 从文件中提取“行”fin.getline(line,sizeof(line))
  • 从“行”中提取“单词”:`std::stringstream word(line);

4 状态标志符的验证

  • eof()ifstream 从类 ios 中继承过来的,当到达文件末尾时返回true
  • bad():如果在读写过程中出错,返回 true 。例如:当我们要对一个不是打开为写状态的文件进行写入时,或者我们要写入的设备没有剩余空间的时候。
  • fail():除了与bad() 同样的情况下会返回 true 以外,加上格式错误时也返回true ,例如当想要读入一个整数,而获得了一个字母的时候。
  • good():如果调用以上任何一个函数返回true 的话,此函数返回 false
  • clear():重置以上成员函数所检查的状态标志

下面是一个eof()的使用示例

   #include <iostream.h>  
   #include <fstream.h>  
   #include <stdlib.h>  
     
   int main () {  
       char buffer[256];  
       ifstream in("test.txt");  
       if (! in.is_open())  
       { cout << "Error opening file"; exit (1); }  
       while (!in.eof() )  		// 状态标志符的验证
       {  
           in.getline (buffer,100);  
           cout << buffer << endl;  
       }  
       return 0;  
   }  
   //结果 在屏幕上输出  
    This is a line.  
    This is another line  

5 获得和设置流指针

所有输入/输出流对象都有至少一个流指针:

  • ifstream, 类似istream, 有一个被称为get pointer的指针,指向下一个将被读取的元素。
  • ofstream, 类似 ostream, 有一个指针put pointer,指向写入下一个元素的位置。
  • fstream, 类似 iostream, 同时继承了get put

我们可以通过使用以下成员函数来读出或配置这些指向流中读写位置的流指针:

  • tellg() 和 tellp()

    这两个成员函数不用传入参数,返回pos_type 类型的值(根据ANSI-C++ 标准) ,就是一个整数,代表当前get 流指针的位置 (用tellg) 或 put 流指针的位置(用tellp)

  • seekg() 和seekp()

    这对函数分别用来改变流指针get 和put的位置。两个函数都被重载为两种不同的原型:

    seekg ( pos_type position );
    seekp ( pos_type position );

    使用这个原型,流指针被改变为指向从文件开始计算的一个绝对位置。要求传入的参数类型与函数 tellg 和tellp 的返回值类型相同。

    seekg ( off_type offset, seekdir direction );
    seekp ( off_type offset, seekdir direction );

    使用这个原型可以指定由参数direction决定的一个具体的指针开始计算的一个位移(offset)。它可以是:

    ios::beg从流开始位置计算的位移
    ios::cur从流指针当前位置开始计算的位移
    ios::end从流末尾处开始计算的位移

流指针 get 和 put 的值对文本文件(text file)和二进制文件(binary file)的计算方法都是不同的,因为文本模式的文件中某些特殊字符可能被修改。由于这个原因,建议对以文本文件模式打开的文件总是使用seekg 和 seekp的第一种原型,而且不要对tellg 或 tellp 的返回值进行修改。对二进制文件,你可以任意使用这些函数,应该不会有任何意外的行为产生。

以下例子使用这些函数来获得一个二进制文件的大小:

// obtaining file size  
   #include <iostream.h>  
   #include <fstream.h>  
     
   const char * filename = "test.txt";  
     
   int main () {  
       long l,m;  
       ifstream in(filename, ios::in|ios::binary);  
       l = in.tellg();  
       in.seekg (0, ios::end);  
       m = in.tellg();  
       in.close();  
       cout << "size of " << filename;  
       cout << " is " << (m-l) << " bytes.\n";  
       return 0;  
   }  
    
  //结果:  
  size of example.txt is 40 bytes.  

6 二进制文件读写

以二进制形式读写文件有哪些好处?

举个例子,现在要做一个学籍管理程序,其中一个重要的工作就是记录学生的学号、姓名、年龄等信息。这意味着,我们需要用一个类来表示学生,如下所示:

class CStudent
{
    char szName[20];  //假设学生姓名不超过19个字符,以 '\0' 结尾
    char szId[l0];  //假设学号为9位,以 '\0' 结尾
    int age;  //年龄
};

如果以文本形式存储学生的信息,则最终的文件中存储的学生信息可能是这个样子:

Micheal Jackson 110923412 17
Tom Hanks 110923413 18
......

要知道,这种存储学生信息的方式不但浪费空间,而且后期不利于查找指定学生的信息(查找效率低下),因为每个学生的信息所占用的字节数不同。

这种情况下,以二进制形式将学生信息存储到文件中,是非常不错的选择,因为以此形式存储学生信息,可以直接把 CStudent 对象写入文件中,这意味着每个学生的信息都只占用 sizeof(CStudent) 个字节。

  • 二进制方式对文件进行读写操作时,打开方式要指定为ios::binary
  • 如果存储类,只占用 sizeof(class) 个字节。
  • 以二进制形式读写文件,<< 和 >> 将不再适用,需要使用 C++ 标准库专门提供的 read() 和 write() 成员方法。其中
  • 二进制文件后缀:.bat

6.1 写文件

流对象调用成员函数 write:函数原型:ostream & write(const char * buffer, int len); 字符指针buffer指向要写入文件的二进制数据的起始位置,len 是读写的字节数,返回一个调用该方法的对象的引用

下面的程序演示了如何将学生信息以二进制形式写入文件:

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

struct CStudent
{
	char szName[20];
	int age;
};
int main()
{
	CStudent s;
	ofstream fout("students.dat", ios::out | ios::binary);
	while (cin >> s.szName >> s.age)  // ^z 结束键入
	{
		fout.write((char*)&s, sizeof(s));
	}
	fout.close();
    
    return 0;
}

6.2 读文件

流对象调用成员函数 read:函数原型:istream & read(char * buffer, int len); 字符指针buffer指向读取字节的起始位置,len 是读写的字节数,返回一个调用该方法的对象的引用

下面程序演示了如何使用 read() 方法将二进制文件数据读取出来:

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

struct CStudent
{
	char szName[20];
	int age;
};
int main()
{
	CStudent s;
	ifstream fin("students.dat", ios::in | ios::binary); //二进制读方式打开
	if (!fin)
	{
		cout << "error" << endl;
		return;
	}
	while (fin.read((char*)&s,sizeof(s)))//一直读到文件结束
	{
		cout << s.szName << " " << s.age << endl;
	}
	fin.close();
    
    return 0;
}

7 缓存和同步

当我们对文件流进行操作的时候,它们与一个streambuf 类型的缓存(buffer)联系在一起。这个缓存(buffer)实际是一块内存空间,作为流(stream)和物理文件的媒介。例如,对于一个输出流, 每次成员函数put (写一个单个字符)被调用,这个字符不是直接被写入该输出流所对应的物理文件中的,而是首先被插入到该流的缓存(buffer)中。

当缓存被排放出来(flush)时,它里面的所有数据或者被写入物理媒质中(如果是一个输出流的话),或者简单的被抹掉(如果是一个输入流的话)。这个过程称为同步(synchronization),它会在以下任一情况下发生:

  • 当文件被关闭时: 在文件被关闭之前,所有还没有被完全写出或读取的缓存都将被同步。
  • **当缓存buffer 满时:**缓存Buffers 有一定的空间限制。当缓存满时,它会被自动同步。
  • **控制符明确指明:**当遇到流中某些特定的控制符时,同步会发生。这些控制符包括:flush 和endl。
  • 明确调用函数sync(): 调用成员函数sync() (无参数)可以引发立即同步。这个函数返回一个int 值,等于-1 表示流没有联系的缓存或操作失败。

参考文档:

成员函数详解:C++输入文件流ifstream用法详解
流的一些总结:c++输入输出流
一文读懂缓冲区
c++输入文件流ifstream用法详解
c++输出文件流ofstream用法详解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1339956.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【鸿蒙千帆起】《钢岚》成为首款基于 HarmonyOS NEXT 开发的战棋新游

近日&#xff0c;紫龙游戏旗下 BlackJack 工作室全新战棋旗舰作品《钢岚》在华为游戏中心首发上线&#xff0c;并宣布《钢岚》完成鸿蒙原生应用开发&#xff0c;成为基于 HarmonyOS NEXT 开发的首款战棋新游&#xff0c;不但进一步丰富了鸿蒙生态战棋品类游戏内容&#xff0c;也…

Java 缓存中间件

Java 缓存中间件 关键词&#xff1a;Spring Cache、J2Cache、JetCache 一 、JSR 107 JSR107 中制订了 Java 缓存的规范。 因此&#xff0c;在很多缓存框架、缓存库中&#xff0c;其 API 都参考了 JSR 107 规范。 img Java Caching 定义了 5 个核心接口 CachingProvider - 定义…

elementui+vue2 input输入框限制只能输入数字

方法1 自定义表单校验 <el-form :model"Formdata" ref"formRef" :rules"nodeFormRules" label-width"100px"><el-form-itemlabel"年龄"prop"age"><el-input v-model.number"Formdata.age&q…

ASUS华硕ROG幻16笔记本电脑2023款GU604VI VZ VY原装出厂Windows11系统22H2

华硕玩家国度幻16笔记本原厂W11系统&#xff0c;适用型号&#xff1a;GU604VI、GU604VZ、GU604VY 链接&#xff1a;https://pan.baidu.com/s/166x6FNUFEpA3Qbzeory3Hg?pwdlwau 提取码&#xff1a;lwau 系统自带所有驱动、出厂主题壁纸、Office办公软件、MyASUS华硕电脑管…

关于使用Selenium获取网页控制台的数据

背景&#xff1a; 需要获取网页的控制台的数据&#xff0c;如下图 在此文章将使用到 Pycharm 和 Selenium4 Pycharm安装 Selenium安装 from selenium import webdriver from selenium.webdriver.common.by import By import time# 创建浏览器对象 browser webdriver.Chro…

Oracle数据updater如何回滚

1.查询update语句执行的时间节点 &#xff1b; select t.FIRST_LOAD_TIME, t.SQL_TEXT from v$sqlarea t where to_char(t.FIRST_LOAD_TIME) > 2023-03-19/17:00:00 order by t.FIRST_LOAD_TIME desc;开启表的行迁移 alter table test enable row movement;3.回滚表数据到…

可运营的Leadshop开源商城小程序源码 +H5公众号+带视频教程

源码简介 Leadshop是一款出色的开源电商系统&#xff0c;具备轻量级、高性能的特点&#xff0c;并提供持续更新和迭代服务。该系统采用前后端分离架构&#xff08;uniappyii2.0&#xff09;&#xff0c;以实现最佳用户体验为目标。 前端部分采用了uni-app、ES6、Vue、Vuex、V…

CamSim相机模拟器:极大加速图像处理开发与验证过程

随着图像处理技术的不断发展&#xff0c;相机模拟在图像处理开发和验证中扮演着越来越重要的角色。相机模拟能够模拟真实相机的成像过程&#xff0c;提供高质量的图像输入&#xff0c;使开发人员能够更好地评估和调整图像处理算法。本文将探讨如何通过相机模拟来加速图像处理的…

2024年煤炭生产经营单位(安全生产管理人员)证考试题库及煤炭生产经营单位(安全生产管理人员)试题解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年煤炭生产经营单位&#xff08;安全生产管理人员&#xff09;证考试题库及煤炭生产经营单位&#xff08;安全生产管理人员&#xff09;试题解析是安全生产模拟考试一点通结合&#xff08;安监局&#xff09;特种…

磁盘管理与文件系统

文章目录 磁盘管理与文件系统一、磁盘基础1、磁盘结构12、磁盘结构2 二、磁盘分区表示1、主引导记录&#xff08;MBR&#xff1a;Mast Boot Record&#xff09;2、磁盘分区表示3、文件系统类型 三、管理磁盘及分区1、检测并确认新硬盘fdisk命令①、查看或管理磁盘分区②、查看常…

如何开发员工管理软件app系统?

我们都知道&#xff0c;不同的业务有多种软件app小程序。但移动软件app小程序不仅可以为企业带来利润&#xff0c;也可以为工作场所的员工带来利润。 您知道小型企业和大型企业都可以从使用的移动软件app小程序中受益。这就是为什么按需软件app小程序开发的需求变得很高的原因…

工具系列:TimeGPT_(6)同时预测多个时间序列

TimeGPT提供了一个强大的多系列预测解决方案&#xff0c;它涉及同时分析多个数据系列&#xff0c;而不是单个系列。该工具可以使用广泛的系列进行微调&#xff0c;使您能够根据自己的特定需求或任务来定制模型。 # Import the colab_badge module from the nixtlats.utils pac…

内网穿透的应用-开源表格工具APITable本地部署结合内网穿透实现公网访问

文章目录 前言1. 部署APITable2. cpolar的安装和注册3. 配置APITable公网访问地址4. 固定APITable公网地址 前言 vika维格表作为新一代数据生产力平台&#xff0c;是一款面向 API 的智能多维表格。它将复杂的可视化数据库、电子表格、实时在线协同、低代码开发技术四合为一&am…

Windows 平台下安装与配置 MySQL 5.7.36

接上文&#xff0c; 配置 MySQL 服务 MySQL 安装完毕之后&#xff0c;需要对服务器进行配置。具体的配置步骤如下。 STEP 01&#xff1a; 在“MySQL安装完成窗口” 单击【Next】&#xff08;下一步&#xff09;按钮&#xff0c;进入服务器配置窗口&#xff0c;如图8所示。选择产…

2024年软考有电子证书吗?如何下载?

并非所有地区都设有软考电子证书制度&#xff0c;目前只有广东、浙江和山东实行了软考电子证书。至于打印时间&#xff0c;由于各地区规定不同&#xff0c;一般在合格标准公布后约一个月左右进行打印。 广东软考电子证书 盖有”广东省人力资源和社会保障厅专业技术人员资格考试…

什么是负载均衡?

负载均衡是指在计算机网络领域中&#xff0c;将客户端请求分配到多台服务器上以实现带宽资源共享、优化资源利用率和提高系统性能的技术。负载均衡可以帮助小云有效解决单个服务器容量不足或性能瓶颈的问题&#xff0c;小云通过平衡流量负载&#xff0c;使得多台服务器能够共同…

1. Spring概述

概述 Spring 是一个开源框架Spring 为简化企业级开发而生&#xff0c;使用 Spring&#xff0c;JavaBean 就可以实现很多以前要靠 EJB 才能实现的功能。同样的功能&#xff0c;在 EJB 中要通过繁琐的配置和复杂的代码才能够实现&#xff0c;而在 Spring 中却非常的优雅和简洁。…

53.网游逆向分析与插件开发-游戏反调试功能的实现-通过内核信息检测调试器

码云地址&#xff08;master分支&#xff09;&#xff1a;https://gitee.com/dye_your_fingers/sro_-ex.git 码云版本号&#xff1a;b44fddef016fc1587eda40ca7f112f02a8289504 代码下载地址&#xff0c;在 SRO_EX 目录下&#xff0c;文件名为&#xff1a;SRO_Ex-通过内核信息…

EasyExcel导出

1.简介 官网&#xff1a;EasyExcel官方文档 - 基于Java的Excel处理工具 | Easy Excel 2.案例 2.1 实现的效果 效果图如下&#xff1a; 2.2 实现步骤 三种情景&#xff0c;主要是表头和数据有区别&#xff0c;简列实现步骤如下&#xff1a; 2.3 具体实现 2.3.1 前置-依赖导入…

探究element-ui 2.15.8中<el-input>的keydown事件无效问题

一、问题描述 今天看到一个问题&#xff0c;在用Vue2element-ui 2.15.8开发时&#xff0c;使用input组件绑定keydown事件没有任何效果。 <template><div id"app"><el-input v-model"content" placeholder"请输入" keydown&quo…