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
类的对象。这个指针可以用来操作对象。由于Son
是Parent
的子类,通过Parent*
指针可以调用Parent
类中定义的函数(如果这些函数不是纯虚函数),也可以调用Son
类中重写的函数(如果存在多态)——如果派生类没有重写纯虚函数,那么试图通过基类指针调用纯虚函数会导致编译错误(因为抽象类的纯虚函数不能被调用,除非在派生类中实现了它)- 由于
Son
是Parent
的子类,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++提供的函数
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
类(cin
是istream
类的一个对象)的开发者提供了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
个字符到 s
所指向的字符数组中,并自动在末尾添加 '\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
是命名空间的名称。::
是作用域解析运算符,用于指定cin
在std
命名空间中。这种写法明确地指出了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
类型的对象file
。fstream
是一个既能进行文件输入又能进行文件输出的文件流类。
打开文件
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
中。
- 使用
关闭文件
- 关闭文件流。用于释放与文件相关的资源。
*注:
-
ios::trunc
的作用- 文件存在时
- 当使用
ios::trunc
模式打开文件时,如果文件已经存在,那么文件中的原有内容将被删除,文件大小变为 0 字节。这就像是把文件 “截断” 了,只保留了文件的元信息(如文件名、权限等),而文件中的数据被清空。
- 当使用
- 文件不存在时
- 如果文件不存在,使用
ios::trunc
模式打开文件会创建一个新的空文件。这是因为ios::trunc
模式本身包含了创建文件的功能,当找不到文件时,它会创建一个新的文件来满足后续的操作需求。
- 如果文件不存在,使用
- 示例代码中的体现
- 在
file.open("mm.text", ios::out | ios::in | ios::trunc);
这行代码中,ios::trunc
与ios::out
(输出模式)和ios::in
(输入模式)一起使用。这意味着程序试图以既可以读又可以写的方式打开mm.text
文件,并且如果文件存在,会先清空文件内容。
- 在
- 文件存在时
-
ios::app
的作用- 追加模式
- 当使用
ios::app
模式打开文件时,无论文件是否存在,所有写入文件的操作都会将数据追加到文件的末尾。这意味着不会覆盖文件中已有的内容。 - 例如,如果文件中已经有了一些数据,使用
ios::app
模式打开后写入新的数据,新数据会被添加到文件原数据的后面。
- 当使用
- 与
ios::trunc
的对比- 与
ios::trunc
不同,ios::app
不会删除文件中的原有内容。ios::trunc
是先清空文件再进行操作,而ios::app
是在文件现有内容的基础上添加新内容。
- 与
- 示例代码中的体现
- 在原代码中
ios
- 在原代码中
- 追加模式