Cpp学习手册-基础学习

首先你要去网上下载对应的运行软件,先把对应的 C++ 环境配置好,配置好了我们就可以开始我们的C++ 学习之旅了。希望通过学习我们能够成为一个比较不错的 C++ 开发工程师。我也会持续更新 C++ 知识。

1. C++语法基础

当我通过 CLion 工具创建了一个新的 Project

示例

cpp 复制代码
#include <iostream>

int main() {
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

问题

  • 这一来我就不理解了 std::cout << "Hello, World!" << std::endl; 这一句语法是什么意思?

    std::cout 是一个预定义的对象,这个对象是 std::iostream 类的一个实例。count 实际上全名为 character output的缩写。当你使用 std::cout 的时候,就是在使用一个输出流对象,它连接到输出设备,通常是你的计算机屏幕。

    接着它会插入运算符(<<)时,会将紧跟在 << 后面的表达式或字符串插入到输出流中。例如,在 std::cout << "Hello, World!"; 中,字符串 "Hello, World!" 被插入到 std::cout 对象中,然后输出到屏幕。

    std::endl 也是 <iostream> 库中的一个预定义标识符,它代表 end line,意味着结束当前行并插入一个换行符。当你在输出流中使用 std::endl 时,它不仅会插入一个换行符,还会刷新输出缓冲区,确保所有待输出的数据都被立即写入到标准输出设备上。

    刷新缓冲区是必要的,因为默认情况下,C++ 可能不会立即显示所有输出。相反,它可能会在内部缓冲输出,直到缓冲区满或遇到某些特定条件才真正写入屏幕。std::endl 确保了即时显示,这对于调试和实时输出特别有用。

  • 为什么要返回一个 0 呢?可不可以用 void main(){} ?

    返回类型 :之所以返回类型是 int。这是因为操作系统期望 main() 函数返回一个整数值,这个值用于指示程序的退出状态。通常,返回 0 表示程序成功执行,非零值则表示程序执行过程中遇到了错误或异常情况。

    返回0的意义 : 当 main() 函数返回0时,它告诉操作系统(或者父进程)程序已经正常终止,没有遇到任何错误。这是一个约定俗成的做法,虽然不同的系统对这个值的具体含义可能有所不同,但在大多数情况下,0被视为成功的标志。

    为什么不能用 void main() : 使用 void main() 是不正确的,因为这违反了C++标准。C++标准规定 main() 函数必须返回一个整数,这主要是为了便于操作系统或父进程判断子进程的执行结果。如果你在某些编译器上看到 void main() 能够编译通过,那是因为编译器可能提供了非标准的扩展,允许这种用法,但这并不符合C++标准,也不可移植。在严格遵循标准的编译器或环境下,void main() 将会导致编译错误。

    argcargv 参数 : 虽然在最简单的程序中,main() 可能只被定义为 int main(), 更常见的形式是 int main(int argc, char *argv[])int main(int argc, char **argv). 这两个参数允许程序接收来自命令行的参数。argc 表示参数的数量(包括程序名称本身),argv 是一个指向字符串数组的指针,其中每个字符串都是一个命令行参数。

变量和数据类型

不同的数据类型:

int, float, double, char, bool, string等。

cpp 复制代码
/**
 * 变量和数据类型
 * @return
 */
int main() {
    int age = 25;
    float height = 1.82f;
    double pi = 3.14159265358979323846;
    char grade = 'A';
    bool isStudent = true;
    std::string name = "John Doe";
    std::cout << name << std::endl;


    std::cout << "Name: " << name << ", Age: " << age << std::endl;
    std::cout << "height: " << height << ", pi: " << pi << std::endl;
    std::cout << "grade: " << grade << ", isStudent: " << isStudent << std::endl;
    return 0;
}

输出

这里面需要注意的是 string 的变量的运用。

声明和初始化变量:

其实初始化变量也是很简单的,对于变量来说无非就是全局变量和局部变量。

全局变量一般都定义在外部,初始化的话直接赋值即可。

局部变量一般定义在函数内部,初始化也很简单,直接赋值即可。

举例

cpp 复制代码
#include <iostream>

int globalVar = 10; // 全局变量的定义和初始化

int main(){
    int localVar = 5; // 局部变量的定义和初始化
    std::cout << "全局变量:" << globalVar << std::endl; // 输出:全局变量:10
    std::cout << "局部变量:" << localVar << std::endl; // 输出:局部变量:5

    return 0;
}

变量的作用域和生命周期:

决定了变量在哪里可被访问,而生命周期则确定了变量存在的时间。

  • 局部变量:在函数内声明的变量,作用域仅限于该函数,生命周期在函数执行完毕后结束。
  • 全局变量:在所有函数外声明的变量,作用域贯穿整个程序,生命周期从程序开始到程序结束。
  • 静态局部变量 :在函数内声明但具有static关键字的变量,作用域限于函数,但生命周期是整个程序。

举例

cpp 复制代码
#include <iostream>

void scopeLifecycle (int& age){
    static int number = 0; // 静态局部变量
    number++;
    age++;

    std::cout << "静态局部变量:" << number << std::endl;
    std::cout << "函数的实参:" << age << std::endl;
}

int main(){
    int localVar = 10;
    scopeLifecycle(localVar);
    scopeLifecycle(localVar);

    std::cout << "外部函数变量: " << localVar << std::endl;
    return 0;
}

输出结果

通过这里我们就可以看见对于静态局部变量来说,它的生命周期是整个程序,如果不是的话,它的值每一次都应该是 1 ,而不是 2

外部变量因为传入的时候,其实是将引用的地址传给了形参,所以导致函数内部修改时,是修改的源数据,如果不加 & 的话,我们是无法修改源数据的。最后应该打印:外部函数变量:10

控制结构

掌握条件语句:

if, else if, else

对于条件语句来说,跟我们学过的语言就是大相径庭的。直接上代码吧。

举例

cpp 复制代码
#include <iostream>

int main() {
    int age;
    std::cout << "请输入心理年龄: ";
    std::cin >> age;
    
    if (age >= 18) {
        std::cout << "欢迎来到大人的世界!" << std::endl;
    } else if (age < 18 && age >= 0) {
        std::cout << "你还是个宝宝!" << std::endl;
    } else {
        std::cout << "你在开玩笑!" << std::endl;
    }

    return 0;
}

输出结果

循环结构:

for, while, do...while ,对于循环结构的学习其实也是跟其它语言类似的,直接上代码吧。

举例

cpp 复制代码
#include <vector>
#include <iostream>
int main() {
    int age;
    // 创建一个空的 vector,可以存储 int 类型的数据(动态列表)
    std::vector<int> numbers;
    do {
        std::cout << "请输入心理年龄(小于等于0时暂停输入): ";
        std::cin >> age;
        if (age >= 18) {
            std::cout << "欢迎来到大人的世界!" << std::endl;
        } else if (age < 18 && age >= 0) {
            std::cout << "你还是个宝宝!" << std::endl;
        } else {
            std::cout << "你在开玩笑!" << std::endl;
        }
        numbers.push_back(age);
    } while (age > 0);

    for (int i = 0; i < numbers.size() - 1; ++i) {
        std::cout << "您已输入年龄(使用for循环):" << numbers[i] << std::endl;
    }

    int i = 0;
    while (i < numbers.size() - 1) {
        std::cout << "您已输入年龄(使用while循环):" << numbers[i] << std::endl;
        i++;
    }
}

输出结果

ser-images%5Cimage-20240816091210276.png&pos_id=img-wN9q5STQ-1724900436681)

switch语句和break, continue控制语句:

switch:

cpp 复制代码
int main(){
    int number;
    std::cout << "请输入校验的数字: ";
    std::cin >> number;
    switch (number) {
        case 1:
            std::cout << "数字为1" << std::endl;
            break;
        case 2:
            std::cout << "数字为2" << std::endl;
            break;
        case 3:
            std::cout << "数字为3" << std::endl;
            break;
        default:
            std::cout << "数字不是 1, 2, 3" << std::endl;
    }
}

输出结果

整体来说这个 switch 的用法也是很简单,但是这里需要注意的一点是,我们需要在每一种 case 的情况之下加入break ,要不然它会在匹配完成之后,继续执行下面 case 的代码。

输出结果

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

continue:

cpp 复制代码
int main() {
    for (int i = 0; i < 5; ++i) {
        if (i == 2) {
            continue;
        }
        std::cout << "i = " << i << std::endl;
    }
    return 0;
}

输出结果

函数

定义和调用函数:

函数的定义

cpp 复制代码
返回类型 函数名(参数类型 参数名, ...) {
    // 函数体
    ...
    return 表达式;
}

调用

cpp 复制代码
函数名(参数值, ...);

参数传递方式:值传递、引用传递:

  • 值传递:将实参的值复制给形参,这样在函数内部对参数所做的修改不会影响到实参。
  • 引用传递:通过引用传递参数,实际上是将实参的地址传递给形参,这样在函数内部对参数的修改会影响到实参。
cpp 复制代码
void swapByValue(int a, int b) { // 值传递
    int temp = a;
    a = b;
    b = temp;
}

void swapByReference(int& a, int& b) { // 引用传递
    int temp = a;
    a = b;
    b = temp;
}

返回值和函数重载:

cpp 复制代码
int add(int a, int b) {
    return a + b;
}

double add(double a, double b) {
    return a + b;
}

数组

创建和初始化数组:

数组在创建时就需要确定其大小,这是因为数组的大小是在编译时期就必须固定的。其它的使用上面来说,还是比较简单的,所以直接上代码。

  • 显式创建数组

    cpp 复制代码
    int arr[] = {1, 2, 3, 4, 5}; // 初始化数组(显式给出初始值)
  • 隐式创建数组

    cpp 复制代码
    int arr[5] = {1, 2, 3}; // 未初始化的元素会被自动初始化为0

访问和操作数组元素:

c++ 复制代码
// 访问和操作数组元素
int main(){
    int arr[] = {10, 20, 30, 40, 50};
    arr[0] = 100; // 修改第一个元素
    int firstElement = arr[0]; // 获取第一个元素
    std::cout << "打印数组第一个元素:" << firstElement << std::endl;
    return 0;
}

多维数组的使用:

多维数组可以表示表格或多维数据集。例如,二维数组可以表示矩阵:

cpp 复制代码
// 多维数组的使用
int main() {
    int matrix[3][3] = {
            {1, 2, 3},
            {4, 5, 6},
            {7, 8, 9}
    };

    // 访问元素
    int element = matrix[1][2]; // 获取第二行第三列的元素
    std::cout << "打印矩阵中第二行第三列元素:" << element << std::endl;
    return 0;
}

2. 指针和引用

指针

C++ 中指针的使用和 C 语言里面都是大相径庭的,都是用 *& 配合使用。

指针是一种变量,它存储的是另一个变量的地址。在 C++ 中,使用星号*来声明指针。

直接上代码

cpp 复制代码
int main() {
    using std::cout;
    using std::endl;

    int *p; // 声明一个指向整数的指针

    // 给指针赋值一个变量的地址,可以使用取地址运算符 &
    int num = 10;
    p = &num; // p现在指向num的地址

    cout << "变量 p 存储的内容:" << p << endl;
    // 使用指针时,可以通过解引用运算符*来访问它所指向的变量的值
    cout << *p << endl;// 输出num的值,即10
    return 0;
}

指针算术和指针作为函数参数:

指针也是可以作为形参,但是如果直接 param+1 的话表示增加指针所指向的地址,使其指向下一个整数位置,而不是简单地增加一个字节。我们需要修改它的值的话还是需要通过 *param 去修改。

cpp 复制代码
void increment(int *param){
    (*param)++;
}

int main(){
    int x = 5;
    increment(&x);
    std::cout << x; // 输出6
    return 0;
}

动态内存分配:newdelete

newdelete 是C++中用于动态内存分配的关键字。new 用于在运行时分配内存,而 delete 用于释放不再需要的内存,以避免内存泄漏。

cpp 复制代码
int *ptr = new int(10); // 分配一个整数内存并初始化为10
*ptr = 20; // 更改值
delete ptr; // 释放内存

空指针和野指针的概念:

  • 空指针 :顾名思义就是指针指向了无效的内存地址,通常这一块内存地址为 NULL 。这样会导致使用这个指针的时候出现未定义行为。
  • 野指针:指向已释放或未分配内存的指针。访问野指针也会导致未定义行为,可能是程序崩溃或其他不可预测的结果。

引用

引用的声明和初始化:

引用是干什么的呢?其实它相当于一个别名,并且在其生命周期内不能重新指向其他变量。引用的声明使用 & 符号,但与取地址运算符不同:

cpp 复制代码
int &ref = num; // ref现在是num的一个别名

引用作为函数参数与返回值:

引用经常用作函数参数,以避免复制大对象的成本,同时允许函数修改传入的对象:

cpp 复制代码
void swap(int &a, int &b) {
    int temp = a;
    a = b;
    b = temp;
}

引用也可以作为函数的返回值,但要注意不要返回局部变量的引用,因为局部变量在函数结束时会被销毁。

引用与指针的区别:

  • 引用在初始化之后是不能更改的,但是指针可以。
  • 引用必须在声明时初始化,而指针可以在任何时候初始化。
  • 引用是不可能为空状态的,但是指针有可能是空的。
  • 引用更安全,因为它们不会导致悬空指针的问题。

3. 面向对象编程(OOP

面向对象编程就是一种编程范式,它是通过抽象出来 "对象" 来设计软件。在 C++ 中,面向对象编程的主要特性包括类和对象、封装、继承和多态。

类和对象

:只是用户定义的一种数据类型,它内部封装了属性和方法。

对象:是类的实例,每一个对象都有属于自己的属性和方法。

cpp 复制代码
#include <iostream>

class Circle {
private:
    float radius;
public:
    float getArea() const  {
        return 3.1415926 * radius * radius;
    }

    void setRadius(float r) {
        radius = r;
    }

    float getRadius() {
        return radius;
    }
};

int main(){
    Circle circle;
    circle.setRadius(5);
    std::cout << "圆的半径为" << circle.getRadius() << " 面积为:" << circle.getArea() << std::endl;
    return 0;
}

这里我想提一下上面的细节

上面在我们的 getArea 函数中,我们的形参后面还有 const 这样一个关键字,那它有什么用呢?

  • const 关键字用于声明一个变量或者表明一个函数不会修改类的成员变量。

  • 在成员函数的上下文中,const 关键字放在函数定义的末尾,紧跟着参数列表,意味着这个成员函数是一个常量成员函数,它保证不修改任何类的非静态数据成员。

对于getArea()这样的成员函数,其主要任务是计算并返回圆的面积,而不需要修改Circle类的任何状态。

const成员函数的好处有什么呢?

  1. 提高代码的安全性:它明确地表示了函数不会修改对象的状态,这有助于防止意外修改数据。
  2. 允许对常量对象的调用 :如果一个Circle对象被声明为const,那么只有它的const成员函数可以被调用。如果没有将getArea()声明为const,那么尝试在一个常量对象上调用它将导致编译错误。
  3. 优化编译器可能的优化 :由于编译器知道const成员函数不会改变对象状态,它可以做出更安全的优化。

构造函数和析构函数:

构造函数的作用就是用于初始化对象。

析构函数的作用就是在对象生命周期结束时调用,用于清理资源。

cpp 复制代码
class Circle {
public:
    Circle() { std::cout << "Circle 对象构建成功!" << std::endl; }
    ~Circle() { std::cout << "Circle 对象销毁成功!" << std::endl; }
};

封装

公有(public)、私有(private)和保护(protected)访问修饰符:

不同的访问修饰符控制了成员的访问级别。

  • public
    • 公有成员对任何代码都是可见和可访问的,无论这些代码是在同一个文件中,还是在另一个文件或另一个类中。
    • 这是默认的继承模式,也就是说,当一个类从另一个类派生时,基类的公有成员在派生类中保持公有。
  • private
    • 私有成员只能被类的成员函数访问,不能从类的外部直接访问。
    • 私有成员对于封装非常重要,因为它们隐藏了类的实现细节,使得类的使用者不必关心这些细节。
    • 在继承中,基类的私有成员不能被派生类访问,也不能被派生类的成员函数访问。
  • protected
    • 保护成员在类的内部和派生类中都是可见和可访问的,但在类的外部不可见。
    • 这意味着派生类可以访问其基类的保护成员,但类的外部代码不可以。
    • 保护成员在实现继承关系时很有用,因为它们允许派生类访问基类的某些实现细节,同时又不让这些细节暴露给最终用户。

使用gettersetter方法:

这个在上面的 Circle 类中也是有的。 一般都是在 pubic 中定义 getXXX 方法或者 setXXX 方法。gettersetter 方法的作用就是提供了一种访问和修改私有属性的方式,增强了封装性。

继承

单继承和多继承:

C++ 当中,被继承的类被称为基类,继承的类被称为派生类。

单继承就是

cpp 复制代码
// 继承:
// 单继承:
class Derived: public Circle {};
int main(){
    Derived derived;
    derived.setRadius(2);
    derived.getRadius();
}
// 多继承:
class Base1 {};
class Base2 {};
class MultiDerived : public Base1, public Base2 {};

虚基类和多重继承冲突解决:

对于虚基类就是为了解决多重继承冲突,它可以用来避免重复基类成员的多次实例化问题。举个例子

cpp 复制代码
class Base {};
class Derived1 : virtual public Base {};
class Derived2 : virtual public Base {};
class MultipleDerived : public Derived1, public Derived2 {}; // 解决多重继承冲突

Derived1 类和 Derived2 类都继承 Base 类, MultipleDerived 类继承 Derived1 类和 Derived2 类 这样我们的MultipleDerived 类就重复继承了Base 类。因为 C++ 可以实现多继承,多继承肯定就会出现这种情况,所以引入虚基类是很有必要的。

多态

虚函数和纯虚函数:

虚函数:虚函数是一个类的成员函数,它允许在派生类中被重写,从而实现多态。

举个例子

cpp 复制代码
class Animal {
private:
    std::string food;
    std::string name;
public:
    Animal(const std::string &f, const std::string &n) {
        food = f;
        name = n;
    }

    virtual void eat() {
        std::cout << name << "喜欢吃:" << std::endl;
    }

    virtual void run() = 0;

    virtual void getLactation() {
        std::cout << "种类" << std::endl;
    }

    void setFood(std::string &f) {
        food = f;
    }

    void setName(std::string &n) {
        name = n;
    }

    std::string getFood() {
        return food;
    }

    std::string getName() {
        return name;
    }
};

class Dog : public Animal {
public:
    Dog(const std::string &n, const std::string &f, const std::string &l) : Animal(f, n) {
        lactation = l;
    }

    void eat() override {
        std::cout << getName() << "正在吃狗粮" << std::endl;
    }

    void run() override {
        std::cout << getName() << "正在奔跑" << std::endl;
    }

    void getLactation() override {
        std::cout << getName() << "所属种类:" << lactation << std::endl;
    }

private:
    std::string lactation;
};

int main() {
    // 使用new创建Dog对象,并通过Animal指针访问
    Animal *animal = new Dog("贵宾犬", "肉", "哺乳动物");
    animal->eat();
    animal->getLactation();
    animal->run();
    // 记得释放内存
    delete animal;
    return 0;
}

这里面需要注意的是

cpp 复制代码
Dog(const std::string &n, const std::string &f, const std::string &l) : Animal(f, n)

派生类构造函数调用基类构造函数是通过初始化列表来完成的,而不是在构造函数体内部调用。

对于纯虚函数来说:

它是在基类中声明的虚函数,其声明形式为 virtual void run() = 0;。由于纯虚函数没有实际的函数体(即没有默认实现),任何包含纯虚函数的类都是抽象类,并且抽象类不能被实例化。这意味着你不能创建抽象类的对象。

所以当你去执行

cpp 复制代码
Animal animal1= new Animal("贵宾犬", "肉");

它是会报错的。

当一个派生类从含有纯虚函数的基类继承时,为了能够实例化这个派生类,它必须提供纯虚函数的具体实现。换句话说,派生类必须重写所有基类中的纯虚函数,否则派生类也将成为一个抽象类,并且不能被实例化。

虚析构函数

在涉及多态和指针的情况下,基类应当声明虚析构函数 ,以确保当删除派生类的实例时,正确的析构函数被调用,避免资源泄露或未定义行为。例如:

cpp 复制代码
class Base {
public:
    virtual ~Base() {} // 虚析构函数
};

运行时多态和静态多态:

运行时多态

通常通过虚函数实现。创建了一个指向基类的指针或者引用,但是开发者可以将衍生类赋值给这个指针,并且调用基类中的虚函数。

在运行时,C++ 会查找实际对象类型对应的函数,并调用那个版本的函数,这就是动态绑定。

cpp 复制代码
#include <iostream>

class Base {
public:
    virtual void func() {
        std::cout << "基类\n";
    }
};

class Derived : public Base {
public:
    void func() override {
        std::cout << "衍生类\n";
    }
};

int main() {
    Base *ptr;

    ptr = new Base();
    ptr->func();  // 输出 "基类"

    ptr = new Derived();
    ptr->func();  // 输出 "衍生类"

    delete ptr;
    return 0;
}

静态多态通常指的是函数重载。

函数重载 允许你在同一个作用域内有多个同名函数 ,只要它们的参数列表不同。编译器在编译时就能确定调用哪个版本的函数,这是基于函数调用时所提供的参数类型和数量。

cpp 复制代码
#include <iostream>

void example(int x) {
    std::cout << "Called with int: " << x << '\n';
}

void example(double x) {
    std::cout << "Called with double: " << x << '\n';
}

int main() {
    example(5);      // 调用example(int)
    example(5.0);    // 调用example(double)
    return 0;
}

4. 输入输出流

标准输入输出流

使用std::cinstd::cout

std::cinstd::cout是预定义的流对象,分别用于标准输入和标准输出。使用std::cin可以从标准输入设备读取数据,而std::cout用于将数据输出到标准输出设备。

举例

cpp 复制代码
int main(){
    int number;
    std::cout << "请输入数字:";
    std::cin >> number;
    std::cout << "输入数字为:" << number << std::endl;
    return 0;
};

格式化输出和输入:

常用方法:
  • 设置宽度setw()
  • 设置填充字符setfill()
  • 设置精度setprecision()
  • 设置浮点数格式fixedscientific
  • 设置布尔值显示boolalpha
示例代码:
cpp 复制代码
#include <iostream>
#include <iomanip>

int main() {
    int number = 42;
    double pi = 3.14159265358979323846;
    double large_number = 1234567890.123456789;
    std::cout << "Number: (no use function setw())"  << number << '\n';
    std::cout << "Number: (use function setw())" << std::setw(42) << number << '\n';

    // 设置填充字符为 '*'
    std::cout.fill('*');
    std::cout << "Number with fill: " << std::setw(10) << number << '\n';
    // 一般 fixed 和 scientific 都配合 setprecision() 方法使用
    // 设置了 std::fixed 和 std::setprecision(n),那么小数点后将保留 n 位数字,不论数值大小如何。
    // 设置了 std::scientific 和 std::setprecision(n),那么小数点后将保留 n 位数字,而数值则以科学记数法的形式显示。
    std::cout << std::fixed << std::setprecision(2) << "Pi (fixed): " << pi << '\n';
    std::cout << std::scientific << std::setprecision(2) << "Large Number (scientific): " << large_number << '\n';

    std::cout << "Boolean: " << std::boolalpha << true << '\n';
    return 0;
}
运行结果:

文件I/O

打开和关闭文件流:

std::ifstream

用于从文件读取数据。

std::ofstream

用于向文件写入数据。

std::fstream

结合了ifstreamofstream的功能,既可以读取也可以写入文件。

读写文本文件和二进制文件:

读写文本文件示例代码:
cpp 复制代码
#include <iostream>
#include <fstream>

int main(){
    std::ofstream out("data.txt");
    out << "Hello, World!\n";
    out.close();

    std::ifstream  in("data.txt");
    std::string line;
    while (getline(in, line)) {
        std::cout << line << '\n';
    }
    in.close();

    return 0;
}
读写二进制文件示例代码:
cpp 复制代码
#include <iostream>
#include <fstream>

struct Data {
    int number;
    double value;
};

int main(){
    Data myData = {123,45.789};
    std::ofstream binaryOut("binary_data.dat", std::ios::binary); // 创建 binaryOut 对象,定义写入的文件和写入的格式。
    // 对我们创建的Data实例的指针进行转换为char类型,然后 binaryOut.write() 函数可以使用这个指针来写入 sizeof(Data) 字节的数据到文件中。
    // 这样,整个结构体 myData 的内存表示就被原封不动地写入到了文件中。
    binaryOut.write(reinterpret_cast<char*>(&myData),sizeof(Data));
    binaryOut.close();

    Data readData;
    std::ifstream binaryIn("binary_data.dat", std::ios::binary);
    binaryIn.read(reinterpret_cast<char*>(&readData),sizeof(Data));
    binaryIn.close();
    std::cout << "Read data: " << readData.number << ", " << readData.value << '\n';  // 输出读取的数据

    return 0;
}

文件定位和错误处理:

文件定位:

可以使用 seekg()seekp() 来改变文件指针的位置,以及使用 tellg()tellp() 来获取文件指针的当前位置。

cpp 复制代码
#include <fstream>
#include <iostream>

int main(){
    std::ofstream out("data.txt");
    out << "Hello, world!\n";
    out.close();


    std::ifstream in("data.txt");
    in.seekg(7); // 移动到文件的第7个字符位置
    char ch;
    in >> ch; // 读取'w'
    std::cout << ch << '\n'; // 输出 'w'
    in.close();

    return 0;
}
错误处理:

可以检查文件流对象的状态位来判断是否发生了错误。例如,eof() 检查是否到达文件末尾,fail() 检查是否有任何错误发生等。

cpp 复制代码
#include <iostream>
#include <fstream>

int main(){
    std::ifstream in("data.txt");
    if (!in) {
        std::cerr << "打开文件出现问题\n";
        return 1;
    }

    // 检查文件读取过程中的错误...
    while (!in.eof()) {
        char ch;
        in >> ch;
        if (in.fail()) {
            std::cerr << "读取文件出现问题\n";
            break;
        }
        std::cout << ch;
    }

    in.close();
    return 0;
}

5. 总结

上面主要从几个方面来进行阐述 C++ 的一些基础的知识和用法,包括 C++语法基础,指针和引用,面向对象编程,输入输出流

希望能对您的学习有帮助!如果有什么问题,欢迎您跟我一起交流交流!

相关推荐
长弓聊编程1 分钟前
Linux系统使用valgrind分析C++程序内存资源使用情况
linux·c++
cherub.8 分钟前
深入解析信号量:定义与环形队列生产消费模型剖析
linux·c++
暮色_年华23 分钟前
Modern Effective C++item 9:优先考虑别名声明而非typedef
c++
重生之我是数学王子31 分钟前
QT基础 编码问题 定时器 事件 绘图事件 keyPressEvent QT5.12.3环境 C++实现
开发语言·c++·qt
我们的五年1 小时前
【Linux课程学习】:进程程序替换,execl,execv,execlp,execvp,execve,execle,execvpe函数
linux·c++·学习
做人不要太理性1 小时前
【C++】深入哈希表核心:从改造到封装,解锁 unordered_set 与 unordered_map 的终极奥义!
c++·哈希算法·散列表·unordered_map·unordered_set
程序员-King.1 小时前
2、桥接模式
c++·桥接模式
chnming19872 小时前
STL关联式容器之map
开发语言·c++
程序伍六七2 小时前
day16
开发语言·c++
小陈phd2 小时前
Vscode LinuxC++环境配置
linux·c++·vscode