C++基础速通笔记-下(持续补充)

2024/11/4 写

速通笔记-上:C++基础速通笔记-上(持续补充)-优快云博客

一、虚函数

1.1虚函数与多态

虚函数

  • 虚函数在基类中声明(在基类函数的声明前加上virtual关键字即可),并且在派生类中可以被重写(Override)
  • Son类中的print()函数没有virtual关键字,但因为它在基类Parent中已经被声明为虚函数,所以它自动成为虚函数。

动态多态性

  • 动态多态性是通过 虚函数 指针引用 实现的。在运行时,根据对象的实际类型来决定调用哪个函数。
  • main()函数中,通过基类指针调用虚函数:(                                                                              ·首先,pparent是一个指向Parent类对象的指针,调用pparent->print()会调用Parent类的print()函数,因为pparent实际指向的是Parent类的对象。                                                        ·然后,parent是一个指向Parent类对象的指针,但它实际上指向的是Son类的对象(通过new Son()创建)。当调用parent->print()时,由于print()是虚函数,C++ 会根据对象的实际类型(即Son类)来决定调用Son类的print()函数。)

输出:

*注:
·构造函数不能是虚函数,因为构造函数执行时,对象的类型尚未完全确定(即对象还未完全构造出来。构造函数的目的是初始化对象,而虚函数的目的是实现多态行为。

1.2抽象类与纯虚函数

·抽象类:类中的每个函数前面都有virtual

        ·抽象类的主要作用是作为一个基类,用于定义一组接口(即一组纯虚函数),这些接口将由派生类通过提供具体实现来继承和实现。由于抽象类不能实例化(不能用来创建对象),它通常用作基类来指导派生类的设计和实现。

        ·抽象类通常用于定义一组操作的规范,这些操作将由派生类根据具体情况来实现。

·纯虚函数:类里面的函数就是纯虚函数,函数声明的末尾加上= 0。它不需要(也不允许)在基类中提供实现。

                ·纯虚函数是面向对象编程中用于定义接口的一个关键概念。主要目的是强制要求从该类派生的 所有子类 都必须提供该函数的具体实现,从而确保派生类具有特定的行为或功能

*注:

·静态数据是继承的

·虚函数被继承后依然是虚函数

Human* man = new Man;

这里使用了基类指针Human*来指向派生类对象Man,这是多态性的体现

运行结果

  • 当运行这段代码时,首先会调用man->empty(),输出empty并返回 0。
  • 然后调用man->print(),输出输出
empty
输出

1.3虚析构函数

        为了析构完整,给析构函数加上virtual,不然出现内存问题——因为如果析构函数不是虚函数,当通过Parent*指针删除对象时,只会调用Parent类的析构函数,而不会调用Son类的析构函数。

输出:

*一些补充

Parent* parent = new Son; :

是一种常见的涉及多态和继承的操作——声明了一个指向Parent类型的指针parent,然后通过new操作符创建了一个Son类的对象,并将这个对象的地址赋给parent指针。

内存分配

  • new Son:这部分在堆上分配了足够的内存来存储一个Son类的对象,并调用Son类的构造函数来初始化这个对象。(这块内存空间的大小是由Son类的大小决定的,它包括Son类自身定义的数据成员所占用的空间,以及从Parent类(如果Son继承自Parent)继承来的数据成员所占用的空间。

 对象创建

  • 在分配内存之后,new操作符会调用Son类的构造函数来初始化这块内存中的对象。如果Son类没有显式定义构造函数,编译器会提供一个默认构造函数。这个默认构造函数会按照数据成员的声明顺序来初始化它们。
  • 由于Son类继承自Parent类,在创建Son类对象时,会先调用Parent类的构造函数。在Parent类中
  • 如果Son类有自己的构造函数,这个构造函数会执行用户定义的初始化操作。例如,如果Son类的构造函数接受参数来初始化sonData,那么在创建对象时可以这样写:Son* son = new Son(5);,这里5就是传递给构造函数用来初始化sonData的参数。
  • new操作符返回的是一个指向新创建对象的指针。在Parent* parent = new Son;这个语句中,虽然声明的指针类型是Parent*,但实际上这个指针指向的是一个Son类的对象。这个指针可以用来操作对象。由于SonParent的子类,通过Parent*指针可以调用Parent类中定义的函数(如果这些函数不是纯虚函数),也可以调用Son类中重写的函数(如果存在多态)——如果派生类没有重写纯虚函数,那么试图通过基类指针调用纯虚函数会导致编译错误(因为抽象类的纯虚函数不能被调用,除非在派生类中实现了它)
  • 由于SonParent的子类,Son类的对象通常会包含Parent类的所有成员(加上Son类自己特有的成员)
  • 总结:当执行new Son时,首先会调用Parent类的构造函数,然后调用Son类的构造函数。这样确保了对象的所有数据成员(包括从基类继承的数据成员)都被正确初始化——new Son操作就是在堆内存中分配空间、构造Son类的对象,并返回指向该对象的指针。

多态行为

  • 这种赋值方式允许通过Parent类型的指针来操作Son类的对象,这是多态的基础。
  • 例如,如果Parent类有一个虚函数void doSomething(),并且Son类重写了这个虚函数,那么通过parent指针调用doSomething()时,会根据对象的实际类型(这里是Son)来调用Son类中的doSomething()实现,而不是Parent类中的实现。
  • 代码中的多态性通过虚函数的定义和重写,以及通过基类指针调用这些虚函数来实现。这种机制使得程序能够在运行时根据 对象的实际类型 来决定调用哪个函数,从而实现了不同对象对同一函数调用做出不同响应的多态行为。

二、IO流

2.1介绍

2.2IO流类

2.3IO流对象

2.4C++提供的函数

更多详解:C++IO流详解_c++的流是什么-优快云博客

1.示例1:标准输入输出流

#include <iostream>
#define _CRT_SECURE_NO_WARNINGS
int main() {
    char name[5];
    std::cin >> name;
    std::cout << name << std::endl;
    // 处理输入缓冲区
    std::cin.ignore();
    std::cout << "请重新输入:";
    std::cin.get(name, 5);
    std::cout << name << std::endl;
    return 0;
}

 输出:

test
请重新输入:
more

处理输入缓冲区

std::cin.ignore();用于忽略输入缓冲区中的一个字符。通常在这里用于处理第一次输入后可能残留的换行符等字符,以确保后续的输入操作不受干扰。

第二次输入和输出(避免潜在问题)

std::cin.get(name, 5);再次从标准输入读取最多 4 个字符到name数组中。

  • 区别一:对空白字符的处理

    • std::cin >> name在读取输入时,遇到空白字符(如空格、制表符、换行符)会停止读取。
    • 例如,如果用户输入 “a b c”,使用std::cin >> name只会读取 “a” 并存储到name数组中。
    • std::cin.get(name, 5)会读取包括空白字符在内的最多 4 个字符。如果用户输入 “a b c”,使用std::cin.get(name, 5)会读取 “a b c”(如果输入的字符总数不超过 4 个)并存储到name数组中。
  • 区别二:缓冲区处理

    • std::cin >> name在某些情况下可能会在输入缓冲区中留下未处理的字符,尤其是当输入的内容超出了目标变量的存储能力时。
    • 例如,如果用户输入了一个较长的字符串,超出了name数组的长度,std::cin >> name可能只会读取部分字符并存储到name数组中,但输入缓冲区中可能还残留着未被读取的字符。这导致后续的输入操作受到影响。
    • std::cin.get(name, 5)在处理缓冲区方面更加稳健。它可以确保读取指定数量的字符,并在必要时处理输入缓冲区中的剩余字符。例如,如果用户输入了一个较长的字符串,超出了name数组的长度,std::cin.get(name, 5)会读取前 4 个字符并存储到name数组中,同时会正确处理输入缓冲区中的剩余字符,避免对后续的输入操作产生干扰。

重载形式的提供者 

 C++ 标准库中,istream类(cinistream类的一个对象)的开发者提供了cin.get的多种重载形式。这些重载函数是 C++ 标准库已经定义好的。作为库的使用者,只需要根据自己的需求调用合适的cin.get重载形式即可

cin.get重载

  cin.get有多种重载形式,其中与cin.get(name, 5)相关的重载形式是istream& get(char* s, streamsize n);

        这里char* s表示指向字符数组的指针,streamsize n表示要读取的最大字符数——这个函数的功能是从输入流中读取最多 n - 1 个字符到 所指向的字符数组中,并自动在末尾添加 '\0' 以形成 C 风格字符串。它返回对输入流的引用,这使得可以进行 链式调用。

与其他重载形式的对比

·与int get();对比

  • int get();这个重载形式是从输入流中读取一个字符,并返回其 ASCII 码值。如果遇到文件末尾(EOF),则返回EOF(通常是 - 1)。它主要用于逐个字符的读取——而cin.get(name, 5)是用于 读取多个字符 并 形成字符串。
  • 例如,int ch = cin.get();是读取一个字符并获取其 ASCII 码,而cin.get(name, 5)是读取多个字符到一个数组

·与istream& get(char& c);对比

  • istream& get(char& c);这个重载形式是读取一个字符并存储到引用参数c所指向的变量中。它主要用于读取单个字符并存储到指定的 变量 ——而cin.get(name, 5)是用于读取多个字符到 一个数组 。
  • 例如,char ch; cin.get(ch);是读取一个字符存储到ch变量中,而cin.get(name, 5)是读取多个字符存储到name数组中。

函数名相同

        cin.get的不同形式都使用了相同的函数名get。在 C++ 中,只要函数名相同并且在同一个作用域内,就有可能是重载。

*(总结)参数列表不同

  • int get();:没有参数,用于获取一个字符的 ASCII 码值。
  • istream& get(char& c);:有一个char类型的引用参数,用于将读取的一个字符存储到该引用所指向的变量中。
  • istream& get(char* s, streamsize n);:有一个char指针参数和一个表示数量的streamsize参数,用于将多个字符读取到字符数组中。
  • 这些不同的参数列表符合 C++ 中函数重载的定义。函数重载允许在同一个类(istream类)中定义多个同名函数,只要它们的参数列表在参数个数、参数类型或者参数顺序上有所不同。在处理不同类型的用户输入(如单个字符选择、字符串输入等)时,可以根据具体情况选择最合适的cin.get重载形式来确保正确的输入操作。

*注:

·当写成std::cin时,std是命名空间的名称。::是作用域解析运算符,用于指定cinstd命名空间中。这种写法明确地指出了cin的来源,即它是std命名空间中的cin对象。(使用using namespace std;时省略

2.示例2:格式控制符

cout<<setiosflages(ios::left) //左对齐
    <<setw(8)<<"姓名" //"姓名在左对齐占8个字节"
    <<setw(8)<<"性别"
    <<setw(8)<<"年龄"<<endl;

三、文件操作

3.1关于文件的头文件

3.2文件基本操作的函数

3.3文件操作初级

#include "标头.h"
#include<fstream> //文件可读可写的类

int main() {
    //打开文件
    fstream file;

    file.open("mm.text", ios::out | ios::in | ios::trunc); //ios::app追加
    //写文件
    file << "Loveyou" << " " << 1001;

    //读文件
    file.seekg(ios::beg);
    char str[10];
    int num;
    file >> str >> num;
    cout << str << ":" << num << endl;

    //关闭文件
    file.close();
    return 0;
}

流程:

头文件

  • #include <fstream>:这个头文件提供了对 文件输入 / 输出流 的支持,用于操作文件。
  • #include "标头.h":这里引用了一个 自定义的头文件 ,可能包含了一些程序需要的声明或定义。

 文件流对象

  • fstream file;:定义了一个fstream类型的对象filefstream是一个既能进行文件输入又能进行文件输出的文件流类。

打开文件

  • ios::out:以输出模式打开文件,用于写入数据。
  • ios::in:以输入模式打开文件,用于读取数据。
  • ios::trunc:如果文件存在,截断文件(删除原有内容)。如果文件不存在,则创建新文件。
  • 注:这里的ios::app是追加模式,在原代码中被注释掉了。如果使用ios::app,数据将被追加到文件末尾,而不是覆盖原有内容。

写入文件

  • 使用<<运算符向文件中写入数据。这里写入了字符串"Loveyou"和整数1001,中间用空格隔开。

读取文件

  • file.seekg(ios::beg);
    • seekg函数用于设置文件读取位置。ios::beg表示将读取位置设置到文件开头。
  • char str[10];int num;
    • 定义了一个字符数组str和一个整数num,用于存储从文件中读取的数据。
  • file >> str >> num;
    • 使用>>运算符从文件中读取数据。先读取字符串到str中,再读取整数到num中。

关闭文件

  • 关闭文件流。用于释放与文件相关的资源。

*注:

  1. ios::trunc的作用

    • 文件存在时
      • 当使用ios::trunc模式打开文件时,如果文件已经存在,那么文件中的原有内容将被删除,文件大小变为 0 字节。这就像是把文件 “截断” 了,只保留了文件的元信息(如文件名、权限等),而文件中的数据被清空。
    • 文件不存在时
      • 如果文件不存在,使用ios::trunc模式打开文件会创建一个新的空文件。这是因为ios::trunc模式本身包含了创建文件的功能,当找不到文件时,它会创建一个新的文件来满足后续的操作需求。
    • 示例代码中的体现
      • file.open("mm.text", ios::out | ios::in | ios::trunc);这行代码中,ios::truncios::out(输出模式)和ios::in(输入模式)一起使用。这意味着程序试图以既可以读又可以写的方式打开mm.text文件,并且如果文件存在,会先清空文件内容。
  2. ios::app的作用

    • 追加模式
      • 当使用ios::app模式打开文件时,无论文件是否存在,所有写入文件的操作都会将数据追加到文件的末尾。这意味着不会覆盖文件中已有的内容。
      • 例如,如果文件中已经有了一些数据,使用ios::app模式打开后写入新的数据,新数据会被添加到文件原数据的后面。
    • ios::trunc的对比
      • ios::trunc不同,ios::app不会删除文件中的原有内容。ios::trunc是先清空文件再进行操作,而ios::app是在文件现有内容的基础上添加新内容。
    • 示例代码中的体现
      • 在原代码中ios
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值