C++基础第八弹
1.C++文件和流
数据类型 | 描述 |
---|---|
ofstream | 该数据类型表示输出文件流,用于创建文件并向文件写入信息。 |
ifstream | 该数据类型表示输入文件流,用于从文件读取信息。 |
fstream | 该数据类型通常表示文件流,且同时具有 ofstream 和 ifstream 两种功能,这意味着它可以创建文件,向文件写入信息,从文件读取信息。 |
打开文件
-
ofstream 和 fstream 对象都可以用来打开文件进行写操作,如果只需要打开文件进行读操作,则使用 ifstream 对象。
-
语法:第一个参数指定要打开的文件的名称或位置 ,第二个参数定义文件被打开的模式
c++void open(const char * filename, ios::openmode mode);
模式标志 描述 ios::app 追加模式。所有写入都追加到文件末尾。 ios::ate 文件打开后定位到文件末尾。 ios::in 打开文件用于读取。 ios::out 打开文件用于写入。 ios::trunc 如果该文件已经存在,就先删除再创建
可以把以上两种或两种以上的模式结合使用。例如,如果想要以写入模式打开文件,并希望重新创建文件,以防文件已存在,那么可以使用下面的语法:
c++
ofstream outfile;
outfile.open("file.dat", ios::out | ios::trunc );
类似地,您如果想要打开一个文件用于读写,可以使用下面的语法:
c++
ifstream afile;
afile.open("file.dat", ios::out | ios::in );
关闭文件
-
当 C++ 程序终止时,它会自动关闭刷新所有流,释放所有分配的内存,并关闭所有打开的文件。
-
下面是 close() 函数的标准语法,close() 函数是 fstream、ifstream 和 ofstream 对象的一个成员。
c++
void close();
写入文件
-
在 C++ 编程中,使用流插入运算符( << )向文件写入信息,就像使用该运算符输出信息到屏幕上一样。
-
唯一不同的是,在这里使用的是 ofstream 或 fstream 对象,而不是 cout 对象。
读取文件
-
在 C++ 编程中,使用流提取运算符( >> )从文件读取信息,就像使用该运算符从键盘输入信息一样。
-
唯一不同的是,在这里使用的是 ifstream 或 fstream 对象,而不是 cin 对象。
读取 & 写入实例
- 写入文件,需要刷新缓冲区
outfile.flush()
- 检查文件是否被打开
c++
//
// Created by 16690 on 2024/4/22.
//
#include <iostream>
#include <fstream>
using namespace std;
int main(void) {
char data[1024];
ofstream outfile;
ifstream infile;
string filename = "D:\\c++\\test.txt";
// 写模式打开文件,并检查文件是否成功打开
// outfile.open(filename);
//追加模式打开
outfile.open(filename,ios::app);
if (!outfile) {
cerr << "Unable to open file for writing." << endl;
return 1;
}
cout << "Writing to the file" << endl;
cout << "Enter your name: ";
cin.getline(data, 1024);
// 向文件写入用户输入的数据,并刷新缓冲区
outfile << data << endl;
outfile.flush(); // 确保数据写入文件
cout << "Enter your age: ";
cin >> data;
cin.ignore(); // 忽略之前输入的换行符
// 再次向文件写入用户输入的数据
outfile << data << endl;
outfile.flush(); // 确保数据写入文件
// 关闭写模式打开的文件
outfile.close();
// 以读模式打开文件,并检查文件是否成功打开
infile.open(filename);
if (!infile) {
cerr << "Unable to open file for reading." << endl;
return 1;
}
cout << "Reading from the file" << endl;
infile.getline(data, sizeof(data)); // 读取字符串
cout << "Name: " << data << endl;
// 读取数据并打印
infile >> data; // 读取数值
cout << "Age: " << data << endl;
// 关闭读模式打开的文件
infile.close();
return 0;
}
文件位置指针
-
istream 和 ostream 都提供了用于重新定位文件位置指针的成员函数。这些成员函数包括关于 istream 的 seekg ("seek get")和关于 ostream 的 seekp("seek put")。
-
seekg 和 seekp 的参数通常是一个长整型。
- 第二个参数可以用于指定查找方向。
- 查找方向可以是 ios::beg(默认的,从流的开头开始定位),
- 查找方向可以是 ios::cur(从流的当前位置开始定位),
- 查找方向可以是 ios::end(从流的末尾开始定位)。
- 第二个参数可以用于指定查找方向。
-
文件位置指针是一个整数值,指定了从文件的起始位置到指针所在位置的字节数
c++
// 定位到 fileObject 的第 n 个字节(假设是 ios::beg)
fileObject.seekg( n );
// 把文件的读指针从 fileObject 当前位置向后移 n 个字节
fileObject.seekg( n, ios::cur );
// 把文件的读指针从 fileObject 末尾往回移 n 个字节
fileObject.seekg( n, ios::end );
// 定位到 fileObject 的末尾
fileObject.seekg( 0, ios::end );
2.C++异常处理
异常是程序在执行期间产生的问题。C++ 异常是指在程序运行时发生的特殊情况,比如尝试除以零的操作。
异常提供了一种转移程序控制权的方式。C++ 异常处理涉及到三个关键字:try、catch、throw。
-
throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
-
catch: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常。
-
try: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch
-
语法:
c++try { // 保护代码 }catch( ExceptionName e1 ) { // catch 块 }catch( ExceptionName e2 ) { // catch 块 }catch( ExceptionName eN ) { // catch 块 }
抛出异常
可以使用throw语句在代码块中的任何地方抛出异常,throw语句的操作数可以是任意的表达式,表达式的结果的类型决定了抛出的异常的类型
c++
double division(int a,int b){
if(b == 0){
cout << "Cannot divide by 0" << endl;
throw "Division by zero condition!";
}
return (a/b);
}
捕获异常
c++
try
{
// 保护代码
}catch( ExceptionName e )
{
// 处理 ExceptionName 异常的代码
}
处理try抛出的任何类型的异常
c++
try
{
// 保护代码
}catch(...)
{
// 能处理任何异常的代码
}
c++
//
// Created by 16690 on 2024/4/22.
//
#include <iostream>
using namespace std;
double division(int,int);
int main(void){
try{
int result = division(10,0);
cout << "result = " << result << endl;
}catch (const char* msg){
cerr << msg << endl;
}
try{
int result = division(10,0);
cout << "result = " << result << endl;
}catch (...){
cerr << "division函数报错" << endl;
}
return 0;
}
double division(int a,int b){
if(b == 0){
cout << "Cannot divide by 0" << endl;
throw "Division by zero condition!";
}
return (a/b);
}
C++标准的异常
Std : exception std : bad_alloc std : bad_cast std : bad_typeid std : bad_exception std : logic_error std : runtime_error std : domain_error std : invalid_argument std : length_error std : out_of_range std : overflow_error std : range_error std : underflow_error
异常 | 描述 |
---|---|
std::exception | 该异常是所有标准 C++ 异常的父类。 |
std::bad_alloc | 该异常可以通过 new 抛出。 |
std::bad_cast | 该异常可以通过 dynamic_cast 抛出。 |
std::bad_typeid | 该异常可以通过 typeid 抛出。 |
std::bad_exception | 这在处理 C++ 程序中无法预期的异常时非常有用。 |
std::logic_error | 理论上可以通过读取代码来检测到的异常。 |
std::domain_error | 当使用了一个无效的数学域时,会抛出该异常。 |
std::invalid_argument | 当使用了无效的参数时,会抛出该异常。 |
std::length_error | 当创建了太长的 std::string 时,会抛出该异常。 |
std::out_of_range | 该异常可以通过方法抛出,例如 std::vector 和 std::bitset<>::operator<>。 |
std::runtime_error | 理论上不可以通过读取代码来检测到的异常。 |
std::overflow_error | 当发生数学上溢时,会抛出该异常。 |
std::range_error | 当尝试存储超出范围的值时,会抛出该异常。 |
std::underflow_error | 当发生数学下溢时,会抛出该异常。 |
定义新的异常
-
通过继承exception ,重写exception方法
-
语法:
c++const char * what () const throw () { //函数体 }
- const char * 表示返回值类型
- what 是函数名称
- () 是参数列表
- const 表示该成员函数不能修改成员变量
- throw() 是异常规格说明符。括号内写该函数可抛出的异常类型
-
const throw() 是异常规格说明,表示 what 函数可以抛出异常的类型,类型说明放到 () 里,这里面没有类型,就是声明这个函数不抛出异常,通常函数不写后面的 throw() 就表示函数可以抛出任何类型的异常
c++
//
// Created by 16690 on 2024/4/22.
//
#include <iostream>
using namespace std;
struct MyException : public exception {
//重写what方法
const char *what() const throw() {
return "C++ Exception";
}
};
int main(void) {
try {
throw MyException();
} catch (MyException &e) {
cout << "MyException caught" << endl;
cout << e.what() << endl;
} catch (exception &e) {
cout << "Other exception" << endl;
cout << e.what() << endl;
}
return 0;
}
3.C++动态内存
-
C++ 程序中的内存分为两个部分:
-
**栈:**在函数内部声明的所有变量都将占用栈内存。
-
**堆:**这是程序中未使用的内存,在程序运行时可用于动态分配内存。
-
-
在 C++ 中,使用new运算符为给定类型的变量在运行时分配堆内的内存,这会返回所分配的空间地址
-
如果不再需要动态分配的内存空间,可以使用 delete 运算符,删除之前由 new 运算符分配的内存
new和delete运算符
-
语法
c++new 数据类型;
c++double* pvalue = NULL; // 初始化为 null 的指针 pvalue = new double; // 为变量请求内存
-
若自由存储区已被用完,可能无法成功分配内存,所以检查new运算符是否返回NULL指针
c++double* pvalue = NULL; if( !(pvalue = new double )) { cout << "Error: out of memory." <<endl; exit(1); }
-
malloc() 函数在 C 语言中就出现了,在 C++ 中仍然存在,但建议尽量不要使用 malloc() 函数。new 与 malloc() 函数相比,其主要的优点是,new 不只是分配了内存,它还创建了对象
c++delete pvalue; // 释放 pvalue 所指向的内存 //基本数据类型,就直接使用delete pvalue,引用数据类型,就使用delete [] pvalue
c++#include <iostream> using namespace std; int main () { double* pvalue = NULL; // 初始化为 null 的指针 pvalue = new double; // 为变量请求内存 *pvalue = 29494.99; // 在分配的地址存储值 cout << "Value of pvalue : " << *pvalue << endl; delete pvalue; // 释放内存 return 0; }
new和delete & malloc和free
- new是在堆区新建一个对象,然后返回该对象的指针
- malloc只是分配一块内存
- delete是释放堆区的对象,然后调用该对象的析构函数
- free是释放堆区的对象,不会调用析构函数
数组的动态内存分配
c++
char* pvalue = NULL;
pvalue = new char[20];
c++
delete[] pvalue;
一维数组
c++
// 动态分配,数组长度为 m
int *array=new int [m];
//释放内存
delete [] array;
二维数组
c++
int **array;
// 假定数组第一维长度为 m, 第二维长度为 n
// 动态分配空间
array = new int *[m];
for( int i=0; i<m; i++ )
{
array[i] = new int [n];
}
//释放
for( int i=0; i<m; i++ )
{
delete [] array[i];
}
delete [] array;
c++
#include <iostream>
using namespace std;
int main()
{
int **p;
int i,j; //p[4][8]
//开始分配4行8列的二维数据
p = new int *[4];
for(i=0;i<4;i++){
p[i]=new int [8];
}
//插入数据
for(i=0; i<4; i++){
for(j=0; j<8; j++){
p[i][j] = j*i;
}
}
//打印数据
for(i=0; i<4; i++){
for(j=0; j<8; j++)
{
if(j==0) cout<<endl;
cout<<p[i][j]<<"\t";
}
}
//开始释放申请的堆
for(i=0; i<4; i++){
delete [] p[i];
}
delete [] p;
return 0;
}
三维数组
c++
int ***array;
// 假定数组第一维为 m, 第二维为 n, 第三维为o
// 动态分配空间
array = new int **[m];
for( int i=0; i<m; i++ )
{
array[i] = new int *[n];
for( int j=0; j<n; j++ )
{
array[i][j] = new int [o];
}
}
//释放
for( int i=0; i<m; i++ )
{
for( int j=0; j<n; j++ )
{
delete[] array[i][j];
}
delete[] array[i];
}
delete[] array;
c++
#include <iostream>
using namespace std;
int main()
{
int i,j,k; // p[2][3][4]
int ***p;
p = new int **[2];
for(i=0; i<2; i++)
{
p[i]=new int *[3];
for(j=0; j<3; j++)
p[i][j]=new int[4];
}
//输出 p[i][j][k] 三维数据
for(i=0; i<2; i++)
{
for(j=0; j<3; j++)
{
for(k=0;k<4;k++)
{
p[i][j][k]=i+j+k;
cout<<p[i][j][k]<<" ";
}
cout<<endl;
}
cout<<endl;
}
// 释放内存
for(i=0; i<2; i++)
{
for(j=0; j<3; j++)
{
delete [] p[i][j];
}
}
for(i=0; i<2; i++)
{
delete [] p[i];
}
delete [] p;
return 0;
}
对象的动态内存分配
c++
#include <iostream>
using namespace std;
class Box
{
public:
Box() {
cout << "调用构造函数!" <<endl;
}
~Box() {
cout << "调用析构函数!" <<endl;
}
};
int main( )
{
//创建包含四个 Box 对象的数组
Box* myBoxArray = new Box[4];
delete [] myBoxArray; // 删除数组
return 0;
}
4.C++命名空间
- 为了区分各个组件库中的函数,然后通过命名空间来解决
定义命名空间
-
命名空间的定义使用关键字 namespace,后跟命名空间的名称
-
语法
c++//创建语法 namespace 命名空间名称{ 代码声明 } //调用语法 using namespace 命名空间名称::code; // code 可以是变量或函数
c++#include <iostream> using namespace std; // 第一个命名空间 namespace first_space{ void func(){ cout << "Inside first_space" << endl; } } // 第二个命名空间 namespace second_space{ void func(){ cout << "Inside second_space" << endl; } } int main () { // 调用第一个命名空间中的函数 first_space::func(); // 调用第二个命名空间中的函数 second_space::func(); return 0; }
using指令
-
使用 using namespace 指令,这样在使用命名空间时就可以不用在前面加上命名空间的名称
-
using 指令引入的名称遵循正常的范围规则。名称从使用 using 指令开始是可见的,直到该范围结束。此时,在范围以外定义的同名实体是隐藏的。
c++#include <iostream> using namespace std; // 第一个命名空间 namespace first_space{ void func(){ cout << "Inside first_space" << endl; } } // 第二个命名空间 namespace second_space{ void func(){ cout << "Inside second_space" << endl; } } using namespace first_space; int main () { // 调用第一个命名空间中的函数 func(); return 0; }
不连续的命名空间
-
命名空间可以定义在几个不同的部分中,因此命名空间是由几个单独定义的部分组成的。一个命名空间的各个组成部分可以分散在多个文件中。
-
如果命名空间中的某个组成部分需要请求定义在另一个文件中的名称,则仍然需要声明该名称。
c++//创建语法 namespace 命名空间名称{ 代码声明 }
嵌套的命名空间
-
命名空间可以嵌套,可以在一个命名空间中定义另一个命名空间
c++namespace 命名空间名称{ 代码声明 namespace 命名空间名称{ 代码声明 } } //调用语法 using namespace第一个命名空间名称::第二个命名空间名称; // code 可以是变量或函数
c++#include <iostream> using namespace std; // 第一个命名空间 namespace first_space{ void func(){ cout << "Inside first_space" << endl; } // 第二个命名空间 namespace second_space{ void func(){ cout << "Inside second_space" << endl; } } } using namespace first_space::second_space; int main () { // 调用第二个命名空间中的函数 func(); return 0; }