一、基础知识
1.基本语法
1.变量和数据类型:整型、浮点型、字符型等
该内容与C语言基础知识和进阶盘点一致,需要查看请查看上一篇文章(C语言基础知识和进阶知识盘点-CSDN博客)
2.运算符:算术运算符、关系运算符、逻辑运算符等
该内容与C语言基础知识和进阶盘点一致,需要查看请查看上一篇文章(C语言基础知识和进阶知识盘点-CSDN博客)
3.控制结构:if-else、switch、循环(for、while、do-while)
该内容与C语言基础知识和进阶盘点一致,需要查看请查看上一篇文章(C语言基础知识和进阶知识盘点-CSDN博客)
2.函数
1.函数的定义和调用
该内容与C语言基础知识和进阶盘点一致,需要查看请查看上一篇文章(C语言基础知识和进阶知识盘点-CSDN博客)
2.参数传递:值传递、引用传递
该内容与C语言基础知识和进阶盘点一致,需要查看请查看上一篇文章(C语言基础知识和进阶知识盘点-CSDN博客)
3.返回值
该内容与C语言基础知识和进阶盘点一致,需要查看请查看上一篇文章(C语言基础知识和进阶知识盘点-CSDN博客)
3.数据结构
1.数组
该内容与C语言基础知识和进阶盘点一致,需要查看请查看上一篇文章(C语言基础知识和进阶知识盘点-CSDN博客)
2.字符串
该内容与C语言基础知识和进阶盘点一致,需要查看请查看上一篇文章(C语言基础知识和进阶知识盘点-CSDN博客)
3.指针和动态内存管理(new和delete)
指针内容与C语言基础知识和进阶盘点一致,需要查看请查看上一篇文章(C语言基础知识和进阶知识盘点-CSDN博客)
#include <iostream>
// 创建一个动态整数数组并返回指针
int* createIntArray(int size) {
// 使用 new 操作符动态分配内存
int* array = new int[size];
// 初始化数组(可选)
for (int i = 0; i < size; ++i) {
array[i] = i * i; // 例如,将每个元素初始化为 i 的平方
}
return array;
}
// 打印数组并释放内存
void printAndDeleteArray(int* array, int size) {
// 打印数组内容
for (int i = 0; i < size; ++i) {
std::cout << array[i] << " ";
}
std::cout << std::endl;
// 使用 delete[] 操作符释放内存
delete[] array;
}
int main() {
int size = 10; // 数组大小
// 创建数组
int* myArray = createIntArray(size);
// 打印并释放数组
printAndDeleteArray(myArray, size);
return 0;
}
4.malloc的底层实现原理
该内容与C语言基础知识和进阶盘点一致,需要查看请查看上一篇文章(C语言基础知识和进阶知识盘点-CSDN博客)
4.输入输出
1.使用cin和cout进行基本输入输出
在C++中,cin 和 cout 是标准输入输出流对象,它们分别用于从标准输入(通常是键盘)读取数据和向标准输出(通常是屏幕)写入数据。这两个对象是由C++标准库中的 <iostream> 头文件提供的
#include <iostream> // 包含输入输出流库
int main() {
int number; // 声明一个整数变量
// 使用 cout 输出提示信息到屏幕
std::cout << "请输入一个整数: ";
// 使用 cin 从键盘读取整数并存储在变量 number 中
std::cin >> number;
// 使用 cout 输出读取到的整数
std::cout << "你输入的整数是: " << number << std::endl;
return 0; // 返回 0 表示程序正常结束
}
2.文件操作(fstream)
在C++中,文件操作是通过<fstream>库来实现的,该库提供了用于读写文件的类。这些类包括ifstream(用于输入文件,即读取文件)、ofstream(用于输出文件,即写入文件)以及fstream(既可以读取也可以写入文件)
#include <iostream>
#include <fstream> // 包含文件操作所需的头文件
#include <string>
/*
1.ifstream:从文件读取数据
2.ofstream:向文件写入数据
4.fstream:结合了ifstream和ofstream的功能,可以双向操作文件
打开文件时,需要指定文件名和打开模式(如读取、写入、追加等)
使用文件流对象读取或写入数据后,应该关闭文件以释放资源
*/
int main() {
// 写入文件
std::ofstream outFile("example.txt"); // 创建一个ofstream对象并打开文件
if (!outFile) { // 检查文件是否成功打开
std::cerr << "无法打开文件进行写入" << std::endl;
return 1; // 如果无法打开文件,则返回错误代码
}
outFile << "这是一个示例文件。" << std::endl; // 向文件写入内容
outFile << "我们正在学习C++的文件操作。" << std::endl;
outFile.close(); // 关闭文件
// 读取文件
std::ifstream inFile("example.txt"); // 创建一个ifstream对象并打开文件
if (!inFile) { // 检查文件是否成功打开
std::cerr << "无法打开文件进行读取" << std::endl;
return 1; // 如果无法打开文件,则返回错误代码
}
std::string line;
while (std::getline(inFile, line)) { // 从文件中逐行读取内容
std::cout << line << std::endl; // 将读取的内容输出到控制台
}
inFile.close(); // 关闭文件
return 0; // 程序成功执行完毕
}
5.面向对象编程(OOP)基础
1.类和对象的定义
类通过class关键字来定义,它通常包含以下几个部分:
1.成员变量(属性):这些变量用于存储对象的状态或数据
2.成员函数(方法):这些函数定义了对象可以执行的操作或行为
3.访问修饰符:这些修饰符(如public、protected、private)用于控制类成员的访问权限
#include <iostream>
class Person {
public:
// 公共成员变量
std::string name;
int age;
// 构造函数
Person(std::string n, int a) : name(n), age(a) {}
// 公共成员函数
void introduce() {
std::cout << "Hello, my name is " << name << " and I am " << age << " years old." << std::endl;
}
private:
// 私有成员变量(示例中未使用,但通常用于封装数据)
// std::string socialSecurityNumber;
// 私有成员函数(通常用于实现类的内部逻辑)
// void calculateSomething() {}
};
int main() {
// 创建Person类的对象
Person person1("Alice", 30);
// 调用对象的成员函数
person1.introduce();
return 0;
}
2.构造函数和析构函数
默认构造函数:在创建对象时自动被调用,其主要任务是初始化对象的成员变量,一个类中可以有多个构造函数(构成重载),非默认构造函数(带有参数的构造函数在初始化时,需要手动调佣)
class MyClass {
public:
// 默认构造函数,无参数
MyClass() {
// 初始化代码
}
// 带参数的构造函数
MyClass(int a, double b) : memberA(a), memberB(b) {
// 使用初始化列表初始化成员变量
}
private:
int memberA;
double memberB;
};
析构函数:在对象生命周期结束时被自动调用,负责执行清理工作,如释放对象所占用的资源
class MyClass {
public:
// ... 类的其他成员函数 ...
// 析构函数
~MyClass() {
// 清理代码,例如释放动态分配的内存
}
};
3.封装:访问修饰符(public、private、protected)
访问修饰符:用于指定类成员的访问权限的关键字,这些修饰符包括public、private和protected,它们定义了类成员(属性和方法)的可见性和可访问性
public:
使用public修饰的成员可以在类的外部被访问
public成员通常用于定义类的接口,即其他类可以调用的方法和可以访问的属性
private:
使用private修饰的成员只能在类的内部被访问
private成员隐藏了类的实现细节,有助于实现封装(Encapsulation)
通过private成员,类可以控制对其内部状态的访问,从而防止外部代码直接修改或访问敏感数据
protected:
使用protected修饰的成员可以在类的内部、派生类(子类)中被访问,但在类的外部不可访问
protected成员提供了一种机制,允许派生类访问基类的某些成员,同时保持这些成员对外部代码的不可见性
protected通常用于基类,以便派生类可以访问基类的某些实现细节,同时防止这些细节被外部代码访问
4.友元关系(友元函数、友元类)
友元关系:是一种非成员访问权限,允许一个类或函数访问另一个类的私有成员和保护成员
友元函数(Friend Function):
一个非成员函数被声明为某个类的友元函数,则它可以访问该类的所有成员(包括私有成员和保护成员)
友元函数不是类的成员函数,它不能通过对象调用,而是直接通过函数名调用
#include <iostream>
using namespace std;
class Box {
private:
double width;
public:
// 构造函数
Box(double w) : width(w) {}
// 声明友元函数
friend void printWidth(Box box);
};
// 定义友元函数
void printWidth(Box box) {
// 友元函数可以访问Box类的私有成员
cout << "Width of box: " << box.width << endl;
}
int main() {
Box box(10.0);
printWidth(box); // 调用友元函数
return 0;
}
友元类(Friend Class):
一个类被声明为另一个类的友元类,则这个友元类的所有成员函数都可以访问另一个类的所有成员(包括私有成员和保护成员)
友元关系不具有传递性,即如果A是B的友元,B是C的友元,并不意味着A是C的友元
#include <iostream>
using namespace std;
// 声明类B为类A的友元类
class B; // 前向声明类B
class A {
private:
int secret;
public:
A(int s) : secret(s) {}
// 声明类B为友元类
friend class B;
// 其他成员函数...
};
// 定义类B,它是类A的友元类
class B {
public:
// 一个成员函数,用于访问类A的私有成员
void revealSecret(A &a) {
cout << "The secret of A is: " << a.secret << endl;
}
// 其他成员函数...
};
int main() {
A a(42); // 创建一个A对象,并初始化其私有成员secret为42
B b; // 创建一个B对象
// 通过B对象的成员函数访问A对象的私有成员
b.revealSecret(a);
return 0;
}
二、进阶知识
1.面向对象编程(OOP)深入
1.继承与多态:虚函数、纯虚函数、虚析构函数
1.继承的概念和定义
继承是一种提高代码复用率的重要方式,它允许程序员在保持原有类的特性的基础上去增加其他特性、功能,这样的类叫做派生类,继承是类设计层次的复用

class Person
{
public:
void Print()
{
cout << "name: " << _name << endl;
cout << "age: " << _age << endl;
}
protected:
string _name = "牡丹";
int _age = 18;
};
class Student :public Person
{
protected:
int _stuid;
};
class Teacher :public Person
{
protected:
int _jobid;
};
int main(void)
{
Student s;
Teacher t;
s.Print();
t.Print();
return 0;
}
基类和派生类对象的复制转换:
派生类对象可以赋值给基类的对象/基类的指针/基类的引用,有一种很形象的说法叫做切片或者切割。就是把派生类当中父类的那部分切来赋值过去
基类的对象不能赋值给派生类:
基类的指针可以通过强制类型转换赋值给派生类的指针,但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(Run-Time Type Information)的dynamic_cast 来进行识别后进行安全转换
class Person
{
protected:
string _name; // 姓名
string _sex; // 性别
int _age; // 年龄
};
class Student : public Person
{
public:
int _No; // 学号
};
void Test()
{
Student sobj;
// 1.子类对象可以赋值给父类对象/指针/引用
Person pobj = sobj;//Person = Student
Person* pp = &sobj;//Person = Student
Person& rp = sobj;//Person = Student
//2.基类对象不能赋值给派生类对象
sobj = pobj;//Student = Person
// 3.基类的指针可以通过强制类型转换赋值给派生类的指针
pp = &sobj;
Student * ps1 = (Student*)pp; // 这种情况转换时可以的。Student* = (Student*)Person*
ps1->_No = 10;
pp = &pobj;//Person =Person
Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题
ps2->_No = 10;
}
继承中的作用域:
1.在继承体系中基类和派生类都有独立的作用域
2.子类和父类中有同名的成员,子类成员和父类成员的关系叫做隐藏,如果非要访问父类的成员,可以添加访问限定符显示访问
3.函数只需要函数名相同就能构成隐藏,参数什么的无关
4.最好不要定义同名的成员
2.重载、覆盖(重写)、隐藏(重定义)

final和override:
在添加父类虚函数后面添加final代表不能再被重写
override代表必须要重写虚函数,如果没有重写便会报错
重载:
#include <iostream>
using namespace std;
// 函数重载示例
void print(int i) {
cout << "Printing int: " << i << endl;
}
void print(double d) {
cout << "Printing double: " << d << endl;
}
int main() {
print(42); // 调用 print(int)
print(3.14); // 调用 print(double)
return 0;
}
重写(override):
#include <iostream>
using namespace std;
// 基类
class Base {
public:
virtual void show() {
cout << "Base class show function" << endl;
}
};
// 派生类
class Derived : public Base {
public:
// 重写基类的虚函数
void show() override {
cout << "Derived class show function" << endl;
}
};
int main() {
Base* b = new Derived();
b->show(); // 调用的是 Derived 类的 show 函数
delete b;
return 0;
}
重定义:
#include <iostream>
using namespace std;
// 基类
class Base {
public:
void func() {
cout << "Base class func function" << endl;
}
};
// 派生类
class Derived : public Base {
public:
// 隐藏基类的 func 函数(参数不同)
void func(int i) {
cout << "Derived class func function with int parameter: " << i << endl;
}
};
int main() {
Derived d;
d.func(); // 错误:没有匹配的函数来调用(因为 Derived::func 需要一个 int 参数)
d.func(42); // 调用 Derived 类的 func(int)
// 如果想调用基类的 func,需要显式地通过基类来调用
Base* b = &d;
b->func(); // 调用 Base 类的 func 函数(通过基类指针)
return 0;
}
3.菱形继承

/*菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在Assistant的对象中Person成员会有两份。
| string _name; ————Person/Student
| int _num; |
| |
| string name; ————Person/Teacher
| int _id; |
| |
| string _majorCourse; |
————————————————Assistant
*/
class Person
{
public:
string _name; // 姓名
};
class Student : public Person
{
protected:
int _num; //学号
};
class Teacher : public Person
{
protected:
int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
string _majorCourse; // 主修课程
};
void Test()
{
// 这样会有二义性无法明确知道访问的是哪一个
Assistant a;
a._name = "peter";
// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";
}
4.继承与友元关系、静态成员
继承与友元:友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员
继承与静态成员:基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例
class Person
{
public :
Person () {++ _count ;}
protected :
string _name ; // 姓名
public :
static int _count; // 统计人的个数。
};
int Person :: _count = 0;
class Student : public Person
{
protected :
int _stuNum ; // 学号
};
class Graduate : public Student
{
protected :
string _seminarCourse ; // 研究科目
};
void TestPerson()
{
Student s1 ;
Student s2 ;
Student s3 ;
Graduate s4 ;
cout <<" 人数 :"<< Person ::_count << endl;
Student ::_count = 0;
cout <<" 人数 :"<< Person ::_count << endl;
}
4.多态的概念和定义
多态:就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会 产生出不同的状态
1.多态构成条件
在继承中要构成多态还有两个条件:
1. 必须通过基类的指针或者引用调用虚函数
2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; } //实现多态的函数必须为虚函数
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; } //可以不写,但是建议写上
};
void Func(Person& p) //要实现多态必须为指针或者引用
{
p.BuyTicket();
}
int main()
{
Person ps;
Student st;
Func(ps);
Func(st);
return 0;
}
5.虚函数
基类函数带vitural,表示子类重写该基类虚函数
#include <iostream>
using namespace std;
// 基类
class Base {
public:
// 定义一个虚函数
virtual void display() {
cout << "Displaying from Base class" << endl;
}
// 虚析构函数(推荐在多态基类中使用)
virtual ~Base() {}
};
// 派生类
class Derived : public Base {
public:
// 覆盖(重写)基类的虚函数
void display() override {
cout << "Displaying from Derived class" << endl;
}
};
int main() {
// 创建基类对象
Base* basePtr1 = new Base();
// 创建派生类对象,但通过基类指针指向它
Base* basePtr2 = new Derived();
// 通过基类指针调用虚函数
// 注意:这里会根据对象的实际类型(运行时类型)来调用正确的函数
basePtr1->display(); // 输出:Displaying from Base class
basePtr2->display(); // 输出:Displaying from Derived class
// 释放内存
delete basePtr1;
delete basePtr2;
return 0;
}
6.纯虚函数
vitural = 0,在基类中声明但没有实现,强制要求派生类必须提供实现,纯虚函数通常用于定义抽象基类(抽象基类是一种特殊的类,它包含至少一个纯虚函数,因此无法被直接实例化。抽象基类的主要目的是为派生类提供一个通用的接口或框架,强制派生类实现某些特定的功能),这样的基类不能实例化,只能作为其他类的基类使用
#include <iostream>
using namespace std;
// 抽象基类
class Shape {
public:
// 声明一个纯虚函数
virtual void draw() const = 0;
// 虚析构函数(在抽象基类中也是推荐的)
virtual ~Shape() {}
};
// 派生类1:圆形
class Circle : public Shape {
public:
// 覆盖(实现)基类的纯虚函数
void draw() const override {
cout << "Drawing a Circle" << endl;
}
};
// 派生类2:矩形
class Rectangle : public Shape {
public:
// 覆盖(实现)基类的纯虚函数
void draw() const override {
cout << "Drawing a Rectangle" << endl;
}
};
int main() {
// 由于Shape是抽象基类,不能直接实例化
// Shape shape; // 这会导致编译错误
// 创建派生类对象
Circle circle;
Rectangle rectangle;
// 使用基类指针指向派生类对象,并调用纯虚函数的实现
Shape* shapePtr1 = &circle;
Shape* shapePtr2 = &rectangle;
shapePtr1->draw(); // 输出:Drawing a Circle
shapePtr2->draw(); // 输出:Drawing a Rectangle
return 0;
}
7.虚析构函数
当你使用基类指针来删除派生类对象时,如果基类的析构函数不是虚函数,那么通过基类指针删除派生类对象时,只会调用基类的析构函数,而不会调用派生类的析构函数,这可能会导致资源泄漏或其他未定义行为
#include <iostream>
#include <cstring>
// 基类
class Base {
public:
// 基类有一个动态分配的资源(例如,一个字符串)
char* data;
// 构造函数分配资源
Base() {
data = new char[10];
std::strcpy(data, "Base");
std::cout << "Base constructor called, data: " << data << std::endl;
}
// 虚析构函数确保派生类的析构函数也被调用
virtual ~Base() {
std::cout << "Base destructor called, deleting data: " << data << std::endl;
delete[] data;
}
// 其他成员函数...
};
// 派生类
class Derived : public Base {
public:
// 派生类也有自己的资源(例如,另一个字符串)
char* moreData;
// 构造函数分配资源
Derived() {
moreData = new char[20];
std::strcpy(moreData, "Derived");
std::cout << "Derived constructor called, moreData: " << moreData << std::endl;
}
// 派生类的析构函数
~Derived() {
std::cout << "Derived destructor called, deleting moreData: " << moreData << std::endl;
delete[] moreData;
}
// 其他成员函数...
};
int main() {
// 使用基类指针指向派生类对象
Base* basePtr = new Derived();
// ... 使用basePtr做一些操作 ...
// 删除对象时,如果Base的析构函数不是虚函数,则只会调用Base的析构函数,导致内存泄漏
// 但由于我们定义了虚析构函数,所以这里会先调用Derived的析构函数,然后调用Base的析构函数
delete basePtr;
return 0;
}
2.动态多态与静态多态
3.类的权限修饰符的深入理解
2.内存管理
1.内存分区:全局区、堆区、栈区、常量区、代码区
2.new/delete与malloc/free的区别
区别:

3.内存泄漏的检测与避免
4.深拷贝与浅拷贝的区别及应用
主要区别在于对于指针的拷贝,假设有一个A对象存在一个a指针,指向D内存。用A对象去初始化一个新的对象B,如果是浅拷贝,那么只会拷贝指针b=a,它们都指向D内存区域。如果是深拷贝,不仅拷贝指针b=a,还会申请一块新的内存空间E,原来的a指向D,新的b指向E

什么时候需要自定义拷贝构造函数?
前面提到浅拷贝和深拷贝的区别在于会不会复制新的内存空间(前提是类中存在指针类成员变量),如果是浅拷贝的状态下,析构A对象,必然会释放D内存;析构B对象时,指针b同样指向D内存,此时D内存早已经被释放了。就出现了同一内存空间被释放两次的情况,系统就会报错
class Person {
private:
char* name;
int* age;
public:
// 构造函数
Person(const char* name, int ageValue) {
this->name = new char[strlen(name) + 1]; // 分配足够的空间并包含 '\0'
strcpy(this->name, name); // 复制字符串
this->age = new int(ageValue); // 分配并初始化整数
}
// 深拷贝构造函数
Person(const Person& other) {
this->name = new char[strlen(other.name) + 1]; // 为新对象分配内存
strcpy(this->name, other.name); // 复制字符串
this->age = new int(*other.age); // 复制整数
}
// 析构函数
~Person() {
delete[] name; // 释放字符串内存
delete age; // 释放整数内存
}
// 输出信息,仅用于演示
void print() const {
cout << "Name: " << name << ", Age: " << *age << endl;
}
// 赋值运算符(可选,为了完整性)
Person& operator=(const Person& other) {
if (this != &other) { // 自赋值检查
delete[] name; // 释放旧内存
delete age;
name = new char[strlen(other.name) + 1]; // 分配新内存
strcpy(name, other.name); // 复制字符串
age = new int(*other.age); // 复制整数
}
return *this;
}
};
int main() {
Person p1("Alice", 30);
Person p2(p1); // 使用深拷贝构造函数
p1.print(); // 输出 Alice, 30
p2.print(); // 输出 Alice, 30
// 修改 p2 的年龄以验证深拷贝
*p2.age = 31;
p2.print(); // 输出 Alice, 31
p1.print(); // 应该仍然输出 Alice, 30,因为 p1 和 p2 拥有独立的内存
return 0;
}
3.高级数据类型
1.引用与指针的深入对比
指针是一个变量,它存储了另一个变量的地址,你可以使用它来访问那个变量
引用是一个别名,它允许你使用另一个名称来访问一个变量
相同点((指针与引用)):
都可以被用来访问其他变量
都可以被赋值为另一个变量的地址或者变量名
区别(指针与引用):
指针是一个独立的变量,而引用必须在创建时被初始化
指针可以被赋值为空指针,而引用不能
指针可以在运行时被赋予不同的值,而引用只能在初始化时被赋值
针与引用如何相互转换?
**指针转引用:**可以使用 & 操作符将指针转换为引用
例如:
int x = 10;
int *p = &x;
int &y = *p; // y 是 x 的引用
**引用转指针:**可以使用 & 操作符将引用变量的地址赋值给指针
例如:
int x = 10;
int &y = x;
int *p = &y; // p 指向 y,也就是 x
指针与引用的关系:
引用本质上就是常指针
引用比指针更安全,因为引用在创建时必须被初始化,并且之后不能再被赋值。这使得引用更加简单和安全。
指针更加的灵活,指针的优点在于它可以在运行时改变指向的内存地址。但是这也同时带来了风险,因为你可能会忘记初始化指针,或者将它赋值为一个无效的内存地址。
所以,选择使用指针还是引用取决于你的需求。如果你需要在运行时改变指向的内存地址,那么使用指针就更合适。如果你只是需要给一个变量取一个别名,那么使用引用就足够了。
2.右值引用与移动语义
3.四种类型转换
1.静态类型转换:static_cast <目标类型> (标识符)
什么时候使用static_cast?
任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast完成C++中基础数据类型转换
2.常类型转换:const_cast <目标类型> (标识符)
何时使用const_cast?
用来移除对象的常量性,即去除const,但只能改变运算对象的底层const
注:只有const_cast能改变表达式的常量属性,使用其他形式的命名强制类型转换改变表达式的常量属性都将引发编译器错误。
3.重解释类型转换:reinterpret_cast <目标类型> (标识符)
何时使用reinterpret_cast?
通常为操作数的位模式提供较低层的重新解释,也就是说将数据以二进制存在形式的重新解释,在双方向上都不可以隐式类型转换的,则需要重解释类型转换,
可以用于任意类型的指针之间的转换,对转换的结果不做任何保证,尽量少用
4.动态类型转换:dynamic_cast <目标类型> (标识符)
何时使用dynamic_cast?
与以上三种类型在编译时就进行类型转换不同,此类型转换发生在程序运行期间
1.用于多态中的父子类之间的强制转化
2.用于将基类的指针或引用安全地转换成派生类的指针或引用
4.四种智能指针(auto_ptr、unique_ptr、shared_ptr、weak_ptr)及其底层实现
4.模板与泛型编程
1.函数模板与类模板的定义与使用
2.模板元编程的基本概念与应用
5.STL(标准模板库)
1.容器的使用与原理(vector、list、map、set等)
容器分为顺序容器、关联容器、容器适配器
1.顺序容器
std::vector,操作与动态数组一样,在最后插入数据;
std::deque,与vector类似,可在开头插入和删除元素;
std::list,操作与双向链表一样,可视为链条,对象被连接在一起,可在任意位置插入和删除对象;
std::forward_list,类似std::list,是单向链表,只能沿着一个方向遍历。
vector和deque是顺序存储结构,而list是链式存储结构,vector和deque都是连续的内存组织,而list则可使用不连续的内存块组织,所以list不需要像vector和deque那样给内部数组重新分配内存。所以在使用中,涉及到频繁插入和删除操作的,一般使用链式存储结构的list,否则一般使用vector足矣。
#include <iostream>
#include <vector>
int main() {
// 创建一个 vector 容器,存储 int 类型的数据
std::vector<int> myVector;
// 在容器尾部插入新元素
myVector.push_back(10);
myVector.push_back(20);
myVector.push_back(30);
// 输出容器中的所有元素
std::cout << "Vector elements: ";
for (int elem : myVector) {
std::cout << elem << " ";
}
std::cout << std::endl;
// 删除容器中的第二个元素(索引为1的元素)
myVector.erase(myVector.begin() + 1);
// 输出删除后的容器中的所有元素
std::cout << "Vector elements after deletion: ";
for (int elem : myVector) {
std::cout << elem << " ";
}
std::cout << std::endl;
// 访问和修改某个特定位置的元素(例如,将第一个元素修改为5)
myVector[0] = 5;
// 输出修改后的容器中的所有元素
std::cout << "Vector elements after modification: ";
for (int elem : myVector) {
std::cout << elem << " ";
}
std::cout << std::endl;
return 0;
}
注:此处用的push_back(),为什么不用emplace_back()
一、push_back函数
功能:在vector容器的尾部添加一个元素
用法:void push_back(const T& x); 其中T是vector中存储的元素类型,x是要添加的元素
原因:push_back是vector提供的一个成员函数,它专门用于在容器的尾部添加元素。由于vector是一个动态数组,它会在需要时自动调整其大小以容纳新元素。使用push_back可以方便地实现向vector中添加元素的操作,而无需手动管理内存或调整容器大小
二、emplace_back函数(C++11及以后)
功能:在vector容器的尾部直接构造并添加一个元素
用法:void emplace_back(Args&&... args); 其中Args&&... args表示传递给新元素构造函数的参数。
原因:与push_back不同,emplace_back会在容器尾部直接构造新元素,而不是先创建元素再拷贝或移动到容器中。这可以减少不必要的拷贝或移动操作,从而提高效率。特别是当元素类型较复杂或拷贝/移动成本较高时,emplace_back的优势更为明显
2.关联容器
std::set,存储各不相同的值,在插入时进行排序;容器的复杂度为对数
std::unordered_set,存储各不相同的值,在插入时进行排序;容器的复杂度为常数
std::map,存储键值对,并根据唯一的键排序;容器的复杂度为对数
std::unordered_map,存储键值对,并根据唯一的键排序;容器的复杂度为对数
std::multiset,与set类似,但允许存储多个值相同的项,即值不需要是唯一的
std::unordered_multiset,与unordered_set类似,但允许存储多个值相同的项
std::multimap,与map类似,但不要求键是唯一的
std::unordered_multimap,与unordered_map类似,但不要求键是唯一的
#include <iostream>
#include <map>
#include <string>
int main() {
// 创建一个 map 容器,键为 std::string 类型,值为 int 类型
std::map<std::string, int> myMap;
// 插入键值对到容器中
myMap["apple"] = 5;
myMap["banana"] = 3;
myMap["cherry"] = 8;
// 输出容器中的所有键值对
std::cout << "Map elements: " << std::endl;
for (const auto& pair : myMap) {
std::cout << pair.first << " => " << pair.second << std::endl;
}
// 查找并输出特定键的值
std::string keyToFind = "banana";
auto it = myMap.find(keyToFind);
if (it != myMap.end()) {
std::cout << "Found " << keyToFind << " with value " << it->second << std::endl;
} else {
std::cout << keyToFind << " not found in map." << std::endl;
}
// 删除一个键值对
myMap.erase("cherry");
// 输出删除后的容器中的所有键值对
std::cout << "Map elements after deletion: " << std::endl;
for (const auto& pair : myMap) {
std::cout << pair.first << " => " << pair.second << std::endl;
}
// 修改某个键的值
myMap["apple"] = 10;
// 输出修改后的容器中的所有键值对
std::cout << "Map elements after modification: " << std::endl;
for (const auto& pair : myMap) {
std::cout << pair.first << " => " << pair.second << std::endl;
}
return 0;
}
2.算法的使用与原理(sort、find、transform等)
STL实现了一系列的模板函数,查找、排序和翻转都是标准的编程需求,不应让程序员重复实现这样的功能,因此,以STL以STL算法的方式提供这些函数,通过结合使用这些函数和迭代器,程序员可对容器执行一些最常见的操作
std::find,在集合中查找值
std::find_if,根据用户提供的谓词在集合中查找值
std::reverse,翻转集合中的元素
std::remove_if,根据用户定义的谓词在集合中删除元素
std::transform,使用用户定义的变换函数对容器中的元素进行变化
注:具体需要哪些算法需要自己上网查找对应的STL网站
3.迭代器的使用与原理(Iterators)
最简单的迭代器是指针,给定一个指向数组中第一个元素的指针,可递增该指针使其指向下一个元素,还可以直接对当前位置的元素进行操作
STL提供的迭代器分为两大类
1.输入迭代器:通过对输入迭代器解除引用,它将引用对象,而对象可能位于集合中,最严格的输入迭代器确保只是以只读的方式访问对象
2.输出迭代器:输出迭代器让程序员对集合执行写入操作,最严格的输出迭代器确保只能执行写入操作
以上两种迭代器可进一步分为三种:
1.前向迭代器,允许输入和输出,还可以是const只读的,前向迭代器通常用于单向链表
2.双向迭代器,可对其进行递减操作,从而向后移动,常用于双向链表
3.随机访问迭代器,常用于访问数组
#include <iostream>
#include <vector>
int main() {
// 创建一个 vector 容器,并初始化一些元素
std::vector<int> myVector = {1, 2, 3, 4, 5};
// 使用迭代器遍历并输出容器中的所有元素
std::cout << "Vector elements: ";
for (std::vector<int>::iterator it = myVector.begin(); it != myVector.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 使用范围 for 循环遍历并输出容器中的所有元素(更简洁的方式)
std::cout << "Vector elements using range-based for loop: ";
for (int value : myVector) {
std::cout << value << " ";
}
std::cout << std::endl;
// 使用迭代器修改容器中的元素
for (std::vector<int>::iterator it = myVector.begin(); it != myVector.end(); ++it) {
*it *= 2; // 将每个元素乘以 2
}
// 输出修改后的容器中的所有元素
std::cout << "Modified vector elements: ";
for (int value : myVector) {
std::cout << value << " ";
}
std::cout << std::endl;
// 使用迭代器插入一个新元素到容器的末尾(虽然通常使用 push_back 更方便)
myVector.insert(myVector.end(), 6); // 但这里为了展示迭代器,我们手动计算位置(实际上是 end())
// 输出插入新元素后的容器中的所有元素
std::cout << "Vector elements after insertion: ";
for (int value : myVector) {
std::cout << value << " ";
}
std::cout << std::endl;
// 使用迭代器删除容器中的某个元素(例如删除值为 4 的第一个元素)
for (std::vector<int>::iterator it = myVector.begin(); it != myVector.end(); ++it) {
if (*it == 4) {
myVector.erase(it); // 注意:erase 后迭代器失效,不能继续使用 it
break; // 我们假设只有一个 4,所以找到后退出循环
}
}
// 输出删除元素后的容器中的所有元素
std::cout << "Vector elements after deletion: ";
for (int value : myVector) {
std::cout << value << " ";
}
std::cout << std::endl;
return 0;
}
4.仿函数
仿函数是一种特殊的类,它可以被当作函数使用
5.容器适配器
容器适配器是顺序容器和关联容器的变种,其功能有限,用于满足特定的需求
std::stack,以后进先出的方式存储元素,能够在栈顶压入和弹出元素
std::queue,以先进先出的方式存储元素,能够删除最先插入的元素
std::priority_queue,以特定的顺序存储元素,因为优先级最高的元素总是位于队列开头
1万+

被折叠的 条评论
为什么被折叠?



