C++面向对象程序设计 - 文件操作与文件流

        在实际应用中,常以磁盘文件作为对象,即能从磁盘文件读取数据,也能将数据输出到磁盘文件,磁盘是计算机的外部存储器,能够长期保留信息,能读能写,可以刷新重写等等。

        在C++中,文件操作通常通过文件流(file streams)来完成,这是<fstream>库提供的功能。fstream库中的三个主要类用于文件操作:ifstream(用于输入文件),ofstream(用于输出文件)和fstream(用于双向文件操作)。

一、文件的概念

        常用的文件有两大类:一类是程序文件(program file),如C++的源程序文件(.cpp)、目标文件(.obj)、可执行文件(.exe)等。一类是数据文件(data fle)在程序运行时,常常需要将一些数据输出到磁盘上存放起来,后期需要时再从磁盘中输入到计算内存,这种磁盘文件就是数据文件。

        文件数据的组织形式,可分为ASCII文件和二进制文件。ASCII文件又称文本(text)文件或字符文件,它的每一个字节放一个ASCII码,代表一个字符。二进制文件又称为内部格式文件或字节文件,是把内存中的数据按其在内存中的存储形式原样输出到磁盘上存放。

        C++提供了低的I/O功能和高级I/O功能。高级的I/O功能是把若干个字节组合为一个有意义的单位(如整数、单精度数、双精度数、字符串或用户自定义的类型的数据),然后以ASCII码字符形式输入和输出(但在传输大容量的文件时由于数据格式转换,速度较慢,效率不高)。低级的I/O功能是以字节为单位输入和输出的,在输入和输出时不进行数据格式的转换,以二进制形式进行的(这种输入输出速度快、效率高、但会感到不方便)。

二、文件流类与文件流对象

        文件流是以外存文件为输入输出对象的数据流,输出文件流是从内存流向外存文件的数据,输入文件是从外存文件流向内存的数据,每一个文件流都有一个内存缓冲区与之对应。

        标准输入输出流istream、ostream和iostream类外,还有3个用于文件操作的文件类:

  1. ifstream类,它是从istream类派生的,用来支持从磁盘文件的输入。
  2. ofstream类,它是从ostream类派生的,用来支持从磁盘文件的输出。
  3. fstream类,它是从iostream类派生的,用来支持从磁盘文件的输入输出。

三、文件的打开与关闭

        对磁盘文件的操作是通过文件流对象(面不是cin和cout)实现的,文件流对象是用文件流类定义的,而不是用istream和ostream类来定义。例如建立一个输出文件流对象:

ofstream outfile;

3.1 打开磁盘

        所谓打开文件是一种形象的说法,打开文件是指在文件读写之前做的必须准备,如:

  1. 为文件流对象和指定的磁盘文件建立关联,以便使文件流流向指定的磁盘文件。
  2. 指定文件的工作方式,如该文件是作为输入文件还是输出文件,是ASCiI文件还是二进制文件等。

        打开文件可以两种形式,具体如下:

1)调用成员函数open形式

文件流对象.open(磁盘文件名, 输入输出方式);

        示例如下:

ofstream outfile;
outfile.open("file.txt", ios::out);

        磁盘文件名可以包括路径,如“c:\new\file.txt”,如缺省路径,则默认为当前目录下的文件。

2)在定义文件流对象时指定参数

        在声明文件流类时定义了带参数的构造函数,其中包括了打开磁盘文件的功能。示例如下:

ofstream outfile("file.txt", out;:out);

        输入输出方式是在ios类中定义的,它们是枚举常量,有多种选择,具体如下表:

方式作用
ios::in以输入方式打开文件
ios::out以输出方式打开文件(这是默认方式),如果已有此名字的文件,则将其原有内容全部清除
ios::app以输出方式打开文件,写入的数据添加在文件末尾
ios::ate打开一个已有的文件,文件指针指向文件末尾
ios::trunc打开一个文件,如果文件已存在,则删除其中全部数据,如文件不存在,则建立新文件。如已指定了ios::out方式,而末指定ios::app、ios::ate、ios::in,则同样默认此方式。
ios::binary以二进制方式打开一个文件,如不指定此方式则默认为ASCII方式
ios::nocreate打开一个已有的文件,如文件不存在,则打开失败。nocreate的意思是不建立新文件
ios::noreplace如文件不存在则建立新文件,如果文件已存在则操作失败,noreplace的意思是不更新原有文件
ios::int|ios::out以输入和输出方式打开文件,文件可读可写
ios::out|ios::binary以二进制方式打开一个输出文件
ios::in|ios::binary以二进制方式打开一个输入文件

        注意:

  1. 新版本 C++系统I/O类库中不提供ios::nocreate和ios::noreplace。
  2. 每一个打开的文件都有一个文件指针,该指针的初始位置由I/O方式指定,每次读写都从文件指针的当前位置开始。每读入一个字节,指针就后移一个字节。当文件指针移到最后,就会遇到文件结束EOF(文件结束符也占一个字节,其值为-1)此时流对像的成员函数eof的值为非0值(一般设为1),表示文件结束。
  3. 要以用“位或”运算符“|”对输入输出方式进行组合。

        示例:

#include <iostream>
#include <fstream>
using namespace std;
int main(){
	ofstream outfile;
	outfile.open("file.txt", ios::app);
	if(!outfile.is_open()){
		clog <<"Open Error" <<endl;
	}
	return 0;
}

        运行如上代码,如file.txt文件不存在,则会输出"Open Error"错误信息,并创建file.txt文件。

3.2 关闭磁盘文件

        在对打开的磁盘文件的读写操作完成后,应关闭该文件。将3.1中代码修改后如下:

#include <iostream>
#include <fstream>
using namespace std;
int main(){
	ofstream outfile;
	outfile.open("file.txt", ios::app);
	if(!outfile.is_open()){
		clog <<"Open Error" <<endl;
	}
	outfile.close();		//关闭流
	return 0;
}

        所谓的关闭,实际上是解除磁盘文件与文件流的关联,原来设置的工作方式也失效,就不能再通过文件流对该文件进行输入或输出。

四、对ASCII文件的操作

        文件的每一个字节中均以ASCII代码形式存放数据,即一个字节存放一个字符,这个文件就是ASCII文件(或称字符文件)。

        对于ASCII文件的读写操作有以下两种方式:

  1. 用流插入运算符"<<"和流提取运算符">>"输入输出标准类型的数据。
  2. 用文件流put, get, getline等成员函数进行字符的输入输出。

4.1 打开文件并输出数据

        下面将一个整形数组,含10个元素,从键盘输入10个整数给数组,将此数组存放到磁盘文件file2.txt中。代码如下:

#include <iostream>
#include <fstream>
using namespace std;
int main(){
	int a[10];
	ofstream outfile("file2.txt", ios::out);			//定义文件流对象,打开磁盘文件"file2.txt"
	if(!outfile){
		cerr <<"open errror!" <<endl;
		exit(1);
	}
	cout <<"enter 10 integer numbers:" <<endl;
	for(int i = 0; i < 10; i++){
		cin >>a[i];
		outfile <<a[i] <<" ";		// 向磁盘文件 file2.txt 输出数据
	}
	outfile.close();				//关闭磁盘文件"file2.txt"
	return 0;
}

        运行结果如下图:

        说明:

  1. 程序中ofstream类定义文件流对象outfile,调用结构函数打开磁盘文件,并向文件中写入数据。另外参数ios::out可以省略,因为输出默认为ios::out。
  2. 如果打开成功,则文件流对象outfile的返回值为非0值,如果打开失败,则返回 值为0(假),加上"!“号非则为真。所以”!outfile“为真,则显示信息并执行exit退出。

4.2 打开文件并输入数据

        下在通过示例,从文件file3.txt文件中读取事前存储好的10个整数,通过输入流读取内容,并计算出其中最大值以及其对应数组索引址。

        file3.txt文件中下图:

        示例代码:

#include <iostream>
#include <fstream>
using namespace std;
int main(){
	int number[10];
	int max, index, temp;
	// 定义输入文件流对象
	ifstream infile("file3.txt", ios::in);
	// 如果输入失败,显示错误信息
	if(!infile){
		cerr <<"open eror~" <<endl;
		exit(1);
	}
	// 循环输入整数
	for(int i = 0; i < 10; i++){
		infile >>number[i];				//将读取整数按顺序存放在number数组中
		cout <<number[i] <<" ";			// 在控制台输出结果
	}
	cout <<endl <<endl;
	// 默认数组中第一个数组最大
	index = 0;
	// 默认先设置最大值为数组第一位元素
	max = number[index];
	// 循环判断最大值
	for(int i = 0; i < 10; i++){
		// 如果最大值小于当前元素值,则将其赋值给max
		if(max < number[i]){
			index = i;
			max = number[i];
		}
	}
	// 
	cout <<"max value:" <<max <<endl <<",index value:" <<index <<endl;
	// 关闭输入流
	infile.close();
	return 0;
}

        运行后结果如下图:

4.3 复制文件

        输出和输出数据前面已了解了,现在可以用已了解的知识,写一个复制文件功能。

        复制文件message.txt文本如下:

Desktop version: Privacy online features carefully protect you. Firefox automatically blocks more than 2,000 trackers from collecting records of your online behavior.
Mobile: No matter where you are, your privacy doesn't have to be compromised - your passwords, search history, open tabs, and other data are safe to take with you.
Enterprise Edition: Tailor product support cycles to your business needs for unparalleled data protection.

        示例代码如下:

#include <iostream>
#include <fstream>
using namespace std;
int main(){
	ifstream infile("message.txt");
	ofstream outfile("messageCopy.txt");
	// 判断是否打开成功
	if(!infile){
		cerr <<"Open message.txt Error";
		exit(1);
	}
	if(!outfile){
		cerr <<"Open messageCopy.txt Error";
		exit(1);
	}
	// 定义变量接收字符
	char ch;
	// 循环读取文件中字符
	while(infile.get(ch)){
		outfile.put(ch);
		cout <<ch;
	}
	cout <<endl;
	
	// 关闭流文件
	infile.close();
	outfile.close();
	
	return 0;
}

        运行后,本来只有message.txt文件的目录,则会多出一个messageCopy.txt文件,并且将message.txt文件中内容全部复制到messageCopy.txt新创建文件中。(注意:ofstream流对象在默认ios::out情况下,则也默认ios:trunc,当文件不存时创建它。这个在本章节”2)在定义文件流对象时指定参数“中,文件输入输出方式设置值表中有说明)。

4.4 复制内容转换为大写

        在4.3的按钮中,复制内容都是英文内容,而且txt文件中存为字符ASCII码。英文字母a~z小写字符对应范围是97~122,大写字母范围则是65~90,所以a与A之间相关为32。

        在了解此规律后,再把上述示例修改一下,将所有复制内容转换为大写后,再复制到新文件中。示例代码如下:

#include <iostream>
#include <fstream>
using namespace std;
int main(){
	ifstream infile("message.txt");
	ofstream outfile("messageUpper.txt");
	// 判断是否打开成功
	if(!infile){
		cerr <<"Open message.txt Error";
		exit(1);
	}
	if(!outfile){
		cerr <<"Open messageCopy.txt Error";
		exit(1);
	}
	// 定义变量接收字符
	char ch;
	// 循环读取文件中字符
	while(infile.get(ch)){
		// 如果发现字母ASCII码值在97~122,则为小写字母,减32将其转换为大写
		if(ch >= 97 && ch <= 122) ch -= 32;
		outfile.put(ch);
		cout <<ch;
	}
	cout <<endl;
	
	// 关闭流文件
	infile.close();
	outfile.close();
	
	return 0;
}

        运行后结果如下:

        此时目录中也会多出messateUpper.txt文件,其中内容已全部转换为大写。

五、对二进制文件的操作

        二进制文件不是以ASCII码存放数据的它将内存中的数据存储形式不加转换地传送到磁盘文件,因此它又称作为内存数据的映像文件。因为文件中的信息不是字符数据,而是字节中的二进制形式的信息,因此它又称为字节文件。

5.1 用成员函数read和write读写二进制文件

        对二进制文件的读写主要用istream类的成员函数read和write来实现。这两个函数的原型为:

istream& read(char *buffer, int len);

ostream& write(const char *buffer, int len);

5.1.1 通过二进制形式存储数据

        在程序中定义一个Student类对象,再定义数组存储三个Student对象数据,然后使用流对象将数组信息通过二进制形式存储起来。示例代码如下:

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

// 定义学生类
class Student{
	private:
		string name;
		int num;
		int age;
	
	public:
		Student(const char* nameStr = "None", int num = 0, int age = 0): num(num), age(age){
			// 使用strncpy来避免缓冲区溢出(注意这里要确保不会超过30个字符)  
			// 如果nameStr可能超过29个字符(需要留一个位置给'\0'),则需要额外的长度检查  
			strncpy(name, nameStr, sizeof(name) - 1); // 复制nameStr到name,并确保最后一个字符是'\0'  
			name[sizeof(name) - 1] = '\0'; // 确保字符串是null终止的
		}
};

int main(){
	//定义学生类对象并初始化数据
	Student list[] = {
		Student("Tom", 1, 18),
		Student("Lily", 2, 19),
		Student("John", 3, 20)
	};
	// 创建输入流
	ofstream outfile("student.data", ios::binary);
	if(!outfile){
		cerr <<"Open file Error" <<endl;
		exit(1);
	}
	// 循环输出学生数据
	for(int i = 0; i < 3; i++){
		outfile.write((char*)&list[i], sizeof(list[i]));
	}
	// 关闭流对象
	outfile.close();
	return 0;
}

        运行后,目录中则会出现student.data文件,使用文本打开发现内容是一堆乱码,这侧是通过二进制保存的数据。如下图:

        上述代码中,&list[i]是结构体数组的一个元素首地址,但这个指向结构体的指针,与形参类型不匹配,因此要用(char *)把它强制转换为字符指针。第二个sizeof(list[i])的值是结构体数组的一个元素的字节数据,sizeof也在之前章节中应用过,并通过它获取过数组的长度,想必大家也并不陌生。

5.1.2 通过二进制读取数据

        能通过二进制存储数据,也可以通过二制制将数据读取回来。现在就将5.1.1中生成的student.data数据读取到内存中,示例代码如下:

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

// 定义学生类
class Student{
	private:
		char name[30];
		int num;
		int age;
	
	public:
		Student(const char* nameStr = "None", int num = 0, int age = 0): num(num), age(age){
			// 使用strncpy来避免缓冲区溢出(注意这里要确保不会超过30个字符)  
			// 如果nameStr可能超过29个字符(需要留一个位置给'\0'),则需要额外的长度检查  
			strncpy(name, nameStr, sizeof(name) - 1); // 复制nameStr到name,并确保最后一个字符是'\0'  
			name[sizeof(name) - 1] = '\0'; // 确保字符串是null终止的
		}
		string getName(){
			return name;
		}
		int getNum(){
			return num;
		}
		int getAge(){
			return age;
		}
};

int main(){
	Student list[3];
	// 定义流对象,读取文件
	ifstream infile("student.data", ios::binary);
	// 判断是否打开成功
	if(!infile){
		cerr <<"Open file error" <<endl;
	}
	for(int i = 0; i < 3; i++){
		infile.read((char *)&list[i], sizeof(list[i]));
	}
	// 关闭流文件
	infile.close();
	// 输出数据
	for(int m = 0; m < 3; m++){
		cout <<"name:" <<list[m].getName() <<endl;
		cout <<"num:" <<list[m].getNum() <<endl;
		cout <<"age:" <<list[m].getAge() <<endl <<endl;
	}
	return 0;
}

        运行后结果如下图:

5.2 与文件指针有关的流成员函数

        在磁盘文件中有一个文件指针,用来指明当前应进行读写的位置。在输入时每读入一个字节,指针就向后移动一个字节。在输出时每向文件输出一个字节,指针就向后移动一个字节,随着输出文件中字节不断增加,指针不断后移。对于二进制文件,允许对指针进行控制,使它按用户的效果图移动到所需的位置,以便在该位置上进行读写。

        文件流提供了一些有关文件指针的成员函数,具体如下表:

成员函数作用
gcount()返回最后一次输入所读入的字节数
tellg()返回输入文件指针的当前位置
seekg(文件中的位置)将输入文件中指针移到指定的位置
seekg(位移量, 参照位置)以参照位置为基础移动若干字节(“参照位置”的用法见说明)
tellp()返回输出文件指针当前的位置
seekp(文件中的位置)将输出文件中指针移到指定的位置
seekp(位移量, 参照位置)以参照位置为基础移动若干字节

        注意:上述函数名中带的"g"或"p",其实g是get的首字母,p是put的首字母。所以带有get的则是用于输入文件,带有put则是用于输出文件。

        参照位置如下表:

名称描述
ios::beg文件开头(beg是begin的缩写),这是默认值
ios::cur指针当前的位置(cur是current的缩写)
ios::end文件末尾

        它们是在ios类中定义的枚举常量。它们使用方法,如下示例:

infile.seekg(100);                //输入文件中的指针向前移到100字节位置
infile.seegg(-50, ios::cur);      //输入文件中的指针从当前位置后移50字节
outfile.seekp(-75, ios::end);     //输出文件中的指针从文件尾后移50字节

5.3 随机访问二进制数据文件

        一般情况下读写是顺序进行的,即逐个字节进行读写。但是对于二进制数据文件来说,可以利用上的成员函数移动指针,随机地访问文件中任一位置上的数据,还可以修改文件中的内容。

5.3.1 reinterpret_cast

        语法形式:

reinterpret_cast<type-id> (expression)

        type-id 必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,再把该整数转换成原类型的指针,还可以得到原先的指针值)。

        该运算符的用法比较多。操作符修改了操作数类型,但仅仅是重新解释了给出的对象的比特模型而没有进行二进制转换。例如:

int *n= new int ;
double *d=reinterpret_cast<double*> (n);

        在进行计算以后, d 包含无用值. 这是因为 reinterpret_cast 仅仅是复制 n 的比特位到 d, 没有进行必要的分析。

5.3.2 static_cast

        static_cast是一个c++运算符,功能是把一个表达式转换为某种类型,但没有运行时类型检查来保证转换的安全性。

        语法形式:

static_cast <type-id>( expression )

        static_cast和reinterpret_cast的区别主要在于多重继承,例如:

#include <cstdio>
class A {
    public:
    int m_a;
};

class B {
    public:
    int m_b;
};
class C : public A, public B {};
//那么对于以下代码:
int main(){
	C c;
	printf("%p,\n%p, \n%p", &c, reinterpret_cast<B*>(&c), static_cast <B*>(&c));
	return 0;
}

        运行结果如下图:

        前两个的输出值是相同的,最后一个则会在原基础上偏移4个字节,这是因为static_cast计算了父子类指针转换的偏移量,并将之转换到正确的地址(c里面有m_a,m_b,转换为B*指针后指到m_b处),而reinterpret_cast却不会做这一层转换。

        编译器隐式执行任何类型转换都可由static_cast显示完成;reinterpret_cast通常为操作数的位模式提供较低层的重新解释。

5.3.3 示例

        将5.1.1和5.1.2中示例代码稍作修改,将数据添加至5条,修改第三条数据的内容,再将其保存到文件中。代码如下:

#include <iostream>  
#include <fstream>  
#include <cstring>  
  
using namespace std;  
  
class Student {  
private:  
    char name[50];  
    int id;  
    int age;  
  
public:  
	Student(){}
    // 构造函数,假设name不会超过49个字符(需要为'\0'留出空间)  
    Student(const char* nameStr, int id, int age) {  
        strncpy(name, nameStr, sizeof(name) - 1);  
        name[sizeof(name) - 1] = '\0';  
        this->id = id;  
        this->age = age;  
    }  
    void setId(int id){
    	this->id = id;
	}
	void setAge(int age){
		this->age = age;
	}
    // 打印函数  
    void print() const {  
        cout << "Name: " << name << ", ID: " << id << ", Age: " << age << endl;  
    }   
};  
  
int main() {  
    // 定义学生数组并初始化数据  
    Student students[5] = {  
        Student("Tom", 1, 18),  
        Student("Lily", 2, 19),  
        Student("John", 3, 20),  
        Student("LiuLei", 4, 18),  
        Student("LiMei", 5, 17)  
    };  
  	string filename = "stu.data";
    // 写入文件  
    fstream file(filename, ios::out | ios::binary | ios::trunc);  
    if (!file) {  
        cerr << "Open file error for writing." << endl;  
        return 1; // 退出程序  
    }  
    for (const auto& student : students) {  
        file.write(reinterpret_cast<const char*>(&student), sizeof(student));  
    }  
    file.close();  
  
    // 读取文件并打印学生信息  
    file.open(filename, ios::in | ios::binary);  
    if (!file) {  
        cerr << "Open file error for reading." << endl;  
        return 1; // 退出程序  
    }  
    Student tempStudent; 
    for (int i = 0; i < 5; i++) {  
        file.read(reinterpret_cast<char*>(&tempStudent), sizeof(tempStudent));  
        tempStudent.print();  
    }  
    file.close();  
  
    // 修改第三个学生的信息  
    students[2].setId(303);  
    students[2].setAge(21);  
  
    // 重新打开文件以进行写入  
    file.open(filename, ios::in | ios::out | ios::binary);  
    if (!file) {  
        cerr << "Open file error for updating." << endl;  
        return 1; // 退出程序  
    }  
  
    // 定位到第三个学生的开始位置  
    file.seekg(2 * sizeof(students[2]), ios::beg); // 跳过前两个学生信息  
    if (file) {  
        // 写入第三个学生的新信息  
        file.write(reinterpret_cast<const char*>(&students[2]), sizeof(students[2]));  
    } else {  
        cerr << "Seek error." << endl;  
    }  
    file.close();  
  
    cout << "Student information has been updated and saved to the file." << endl;  
    // 再次读取文件并显示所有学生的信息  
    file.open(filename, ios::in | ios::binary);  
    if (!file) {  
        cerr << "Open file error for reading again." << endl;  
        return 1; // 退出程序  
    }  
    cout << "Updated student information:" << endl;  
    for (int i = 0; i < 5; i++) {  
        Student tempStudent;  
        file.read(reinterpret_cast<char*>(&tempStudent), sizeof(tempStudent));  
        tempStudent.print();  
    }  
    file.close(); 
  
    return 0;  
}

        运行后结果如下图:

      

注意几点:

  1. 我们使用了fstream而不是分别使用ofstream和ifstream,因为我们要在同一个文件中进行读写操作。
  2. 使用seekg来定位到文件中第三个Student对象的开始位置。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Java】Java18的新特性

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

第四篇红队笔记-百靶精讲之Prime-wfuzz-wpscan-openssl enc

靶机Prime渗透 主机发现 nmap扫描与分析 目录爆破与模糊测试 dirb 目录扫描 dev secret.txt wfuzz发现 file参数 根据secret.txt-location.txt 和 file参数结合 secrettier360 根据filelocation.txt得到的on some other php page&#xff08;改用之前扫到image.p…

mqtt-emqx:设置遗嘱消息

【pom.xml】 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.3.12.RELEASE</version> </dependency> <dependency><groupId>org.eclipse…

新书推荐:2.2.4 第11练:消息循环

/*------------------------------------------------------------------------ 011 编程达人win32 API每日一练 第11个例子GetMessage.c&#xff1a;消息循环 MSG结构 GetMessage函数 TranslateMessage函数&#xff1a;将虚拟键消息转换为字符消息 DispatchMessage函数…

Vue根据后端返回的tabList动态渲染组件信息

最近做了一个功能&#xff0c;后端根据配置信息&#xff0c;动态返回一个tabList&#xff0c;其中结构是List<String,Object> tabList; map里面的数据是 label、value 页面需要根据tablist动态渲染组件&#xff08;不同的tab都使用了组件进行了封装&#xff09; 实现效果…

解决福昕风腾PDF套装无法打印在线电子签章的方法

使用福昕风腾PDF套装打印在线电子签章文件时发现&#xff0c;在线盖的电子印章和签名却打印不出来&#xff0c;后现发现&#xff0c;按图中选项选择“文档”&#xff0c;即可完整打印文件内容及电子签章。留印。

【PL理论】(8) F#:列表高阶函数之 filter 函数 | 内联谓词函数 | 链式操作:先过滤再映射

&#x1f4ad; 写在前面&#xff1a;上一章中&#xff0c;我们详细讲解了列表的合并&#xff0c;本章我们来详细讲解一下列表的过滤&#xff0c;在 F# 中&#xff0c;过滤列表是指从列表中提取满足某个条件的元素&#xff0c;形成一个新的列表。这个操作通常使用 List.filter 函…

超详解——Python模块文档——小白篇

目录 1. Unix起始行 示例&#xff1a; 2. 对象和类型 示例&#xff1a; 3. 一切都是对象 示例&#xff1a; 4. 理解对象和引用 示例&#xff1a; 5. 理解对象和类型 示例&#xff1a; 6. 标准类型 示例&#xff1a; 7. 其他内建类型 示例&#xff1a; 8. 类型的类…

HTML静态网页成品作业(HTML+CSS)—— 保护环境环保介绍网页(1个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有1个页面。 二、作品演示 三、代…

基于zyyo主页与無名の主页合并二改,一款适合新手的个人主页

pengzi主页&#x1f64b; 项目地址 简洁的布局&#xff1a;主页应该有清晰的布局&#xff0c;包括一个简洁的导航菜单和易于浏览的内容区域。避免使用过多的花哨效果&#xff0c;保持页面简洁明了。 个人资料介绍&#xff1a;在主页上展示一段简短的个人介绍&#xff0c;包括…

bat脚本简介

一、bat脚本 概念定义 BAT 批处理是一种在 Windows 系统中用于将一系列命令组合成一个可执行文件&#xff08;.bat 文件&#xff09;的脚本技术。 允许用户将多个操作命令按顺序编写在一起。形成一个自动化执行的流程。批处理文件可以包含各种系统命令和程序调用。 如文件操作…

STM32_HAL库_外部中断

一、设置分组 stm32f1xx_hal_cortex.c 查看分组 五个形参&#xff0c;分组0~4 stm32f1xx_hal.c 设置了分组为2&#xff0c; 此工程就不需要再设置了 再回到stm32f1xx_hal_cortex.c 查看NVIC_SetPriorityGrouping的定义&#xff0c;若无法跳转&#xff0c;先编译一下&…

海外电商平台的开发对接

对接海外第三方电商平台是一个复杂但至关重要的过程&#xff0c;尤其是对于那些希望在全球市场拓展业务的跨境电商企业。以下是对接海外电商平台的一般步骤和技术要点。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1. 平台选择 确…

最新 HUAWEI DevEco Studio 使用技巧

最新 HUAWEI DevEco Studio 使用技巧 HUAWEI DevEco Studio 作为我们 harmonyos 应用的开发工具&#xff0c;有必要好好打磨一下。 Chinese(Simplified) 中文汉化插件 GitToolBox 编辑器中显示git历史 保存时自动格式化 写了一堆代码&#xff0c;当保存时&#xff0c;自动帮…

【docker】centos7配置docker镜像阿里云加速

国内从 DockerHub 拉取镜像有时会遇到困难&#xff0c;由于网络原因&#xff0c;下载一个Docker官方镜像可能会需要很长的时间&#xff0c;甚至下载失败。此时可以配置镜像加速器。Docker 官方和国内很多云服务商都提供了国内加速器服务。 测试了几次阿里云的加速是最快的。 …

安装 JDK 17

安装包 百度网盘 提取码&#xff1a;6666 安装步骤 双击下载得到的安装包&#xff0c;开始安装&#xff1a; 正在安装&#xff1a; 安装完成&#xff1a; 安装路径下&#xff0c;多出来了很多新的内容。安装文件夹所包含的内容及作用&#xff1a; src 是 JDK 的源码包。类库…

go语言后端开发学习(二)——基于七牛云实现的资源上传模块

前言 在之前的文章中我介绍过我们基于gin框架怎么实现本地上传图片和文本这类的文件资源(具体文章可以参考gin框架学习笔记(二) ——相关数据与文件的响应)&#xff0c;但是在我们实际上的项目开发中一般却是不会使用本地上传资源的方式来上传的&#xff0c;因为文件的上传与读…

初级软件测试快速入门

文章目录 初级软件测试-测试用例、缺陷报告的认识与使用软件测试简介测试分类模型质量模型测试模型 用例编写的八大要素用例设计方法缺陷 初级软件测试-测试用例、缺陷报告的认识与使用 软件测试简介 什么是软件测试&#xff1f; 使用技术手段验证软件是否满足需求 主流技能 …

【香橙派】Orange Pi AIpro体验——国产AI赋能

文章目录 &#x1f354;开箱&#x1f6f8;烧录镜像⭐启动系统&#x1f388;本机登录&#x1f388;远程登陆 &#x1f386;AI功能体验&#x1f50e;总结 &#x1f354;开箱 可以看到是很精美的开发组件 这里是香橙派官网 http://www.orangepi.cn/ 我们找到下面图片的内容&#…