c++特性

编程概念

c++高级编程过程:源文件(*.cpp)→编译→目标文件(*.obj)→链接→可执行文件(*.exe)

容其中链接的过程是为了将头文件与目标文件结合起来。

c++输入输出流

头文件:iostream(必须包含)

输入流:将设备输入的内容插到程序到中 ,接收外部信息。用cin的代码进行插入,而》为提取

输出流:将程序的内容显示到设备中,发送信息到外部。用 cout的代码进输出,而《为插入。常常用于发送表达式的结果,因此可以将表达式放入其中。

endl:常与cin和cout结合,用于换行,多个endl可以换多个行。

主函数:main,一般将代码放入主函数中进行一一执行。

内联函数

概念:将函数直接生成在调用的位置,可以节省调用的时间,但会耗费多的空间,因此是以空间换时间的方式。

语法:在函数前面加上inline

与普通函数的区别:

阶段 内联函数 普通函数
预处理 不处理 不处理
编译 可能将代码插入调用处 生成函数调用指令
链接 无额外操作 解析函数地址
调用 在调用处直接插入代码 跳转到函数

案例:

复制代码
#include<iostream>
using namespace std;
inline int func(int x, int y)//内联函数
{
	return x + y;
}
int main()
{ 
    int a,b;
    cin>>a>>b;
	cout << func(a, b) << endl;//具备输出命令和换行
}

重载函数

概念:在一个项目,需要用同样的函数名表达类似的含义,就可以通过重载函数的方式。系统是根据参数来区分应该调用哪一个函数

特点:函数名相同,参数个数或者参数类型不同。但是最重要的是函数类型或返回值不同但参数相同就无法构成。

指针

前置知识

地址:在系统中为变量设置的每字节空间的编号,如果在程序中定义了一个变量或数组,那么就会随机生成地址,这个变量放入这个地址,这个变量或数组的地址就确定为一个常量。

直接访问:创建变量后,存在某个地址中,但访问时,是直接对变量本身进行访问

复制代码
 int a = 0;
cout << a << endl;//直接访问

间接访问:将变量的地址放入指针中,通过指针对变量进行访问。

指针概念

将地址作为变量存储的对象,一个变量的地址可以称为变量的指针

语法

类型*变量名。表示只能存放这类型变量的地址。*表示指向,&表示取地址。对地址或指针取(*)可以对地址所对应的变量进行访问。

应用

指针变量作为函数参数:可以将函数外部的变量地址传入函数内部,就可以在函数内部更改外部变量,本质上就是将变量在内存的地址传给函数 。但是如果不传地址,只普通传变量,这个函数就会创建新的地址来存放这个变量,无法对外部变量进行更改,只会获得外部变量的值。本质上就是传递的具体值。

指针指向数组:本质上就是指向数组的第一个元素的地址,而对指针++,便会移动这个数组类型大小的字节。

引用

概念

对一个以及创建好的对象,取别名,它们共用一块地址。这块地址两个名字。

语法

<类型> &<引用变量名>=****<原变量名>

应用

  1. 对一个具体变量取别名 int& s=a
  2. 对一块手动开辟好的空间取别名 int& s = *new int

要点

  1. 引用定义时必须要引用一个创建好具体的变量
  2. 引用一旦定义好了,就不能更改指向的变量
  3. 不能针对常量进行引用,除非这个引用加上const (const int& s = 10;)
  4. 不能建立引用数组
  5. 不能建立引用指针
  6. 不能建立引用的引用

指针和引用的区别

  1. 指针是通过地址间接访问变量的,而引用是直接访问变量
  2. 指针初始化可以不设置具体变量,并且可以更换指向的地址。而引用初始化必须要指定变量,并且一旦指定不能更改

函数中的引用

  1. 作为函数参数时,可以直接对外部实参变量操作
  2. 作为函数返回值时,就是对函数内部某一个返回值取别名,因此与这个返回值的地址是一样的,因此这个返回值的生命周期不能只在这个函数内部,否则会报错。 要点:作为返回值的引用,要考虑这个引用是在函数运行结束之后产生的,所有不能返回形参和自动变量。返回的变量必须为全局变量或者静态变量。返回值为引用的函数可以做左值,可以对其进行赋值和操作,本质的就是操作的是取别名的那个对象。

const类型变量

概念:对变量进行加上const修饰,表示这变量变成了常量,不能对其进行更改,只能访问。

应用:

1.禁写指针

表明这个指针指向其它变量的地址

2.禁写间接引用

表明不能通过对指针解引用去修改a的值

面向对象

概念:是一种编程风格,将现实世界的事物和概念抽象成计算机程序的对象。主要思想是把构成问题的各个食物分解成各个对象,建立的对象的目的不是完成一个步骤,而是为了描述一个事务在解决问题中充当的角色,需要执行的行为。面向对象程序设计中的概念主要包括:对象(现实的实体)、 类(对实体的描述)、数据抽象(隐藏实体描述的细节)、继承(同类对象,但部分不同)、动态绑定(根据具体场景来表达对象的方法)、数据封装(把对象打包)、多态性(同一个操作作用于不同的对象,可能具有不同的方法)、消息传递(对象直接可以进行交互)。

程序设计语言

面向对象的语言:具备封装、继承多态的特性。执行任务时,需要一个对象去执行某一行为。公式组成:算法+数据结构=对象 程序=对象+对象+....+消息。消息用于对对象的控制,以及相关联。类是对 象的抽象,而对象是类的具体实例(instance)。

基于对象(面向过程)的语言:使用函数去执行完成任务中需要执行的某一步骤。公式组成:算法+数据结构=程序

类的实现

在面向对象的风格中,所有数据都封装在类中,完成任务,只需要通过创建类的实例或者少量新类进行通信操作,来完成任务

类的应用

类一般由属性和行为组成

属性:属于静态的。类似于一个人的外貌身高

行为:属于动态的。类似于一个人会打篮球、踢足球

面向对象创建程序的思路为:首先完成这个任务需要那些对象,而这些对象有那些的属性和行为能对完成任务有帮助。而属性就是类中的成员变量,而行为就是类中的成员函数。因此人们设想把相关的数据和操作放在一起,形成一个整体,与外界相对分隔。这就是 面向对象的程序设计中的对象。

C++类

类的定义

复制代码
class 类名
{
  private:
  私有成员数据;
  私有成员函数;
  public:
  公有成员数据;
  公有成员函数;
  protected:
  保护成员函数;
  保护成员数据;

};

private:只能在类内部使用,在外部不能使用,如果需要使用成员数据,需要通过公有函数来实现。默认不加限定词就是私有。

public:作用域在类内部和外部,不受类的限制

protected:作用域在类内部和派生类内部,即该类和该类的派生类

其中成员函数可以在外部定义,类内部声明

语法:

复制代码
<type>   < class_name > :: < func_name > (<参数表>)
 {
 ......  //函数体
}

如果函数在类内部定义就相当于内联函数,如果在外部定义时加上inline也是内联函数。并且函数的参数可以设置为缺省,不够缺省的参数要从右往左进行放置

类成员的访问和使用

私有和保护成员:在类内部可以直接使用,但在外部需要通过公有函数的结合来获取或者是更改。

公有成员:普通类的实例化可以通过.来获取,指针型的实例化通过->来获取

类作用域、类类型的作用域和对象的作用域

类的成员函数和成员数据,在类内部可以直接调用,出了类,在外部调用需根据成员的特性以及结合类的实例化进行调用,不能单独调用

类类型的作用域根据所定义的位置,在函数内部,表明作用域只能在函数内部使用,而在函数外部定义就可以全局使用

类的对象作用域就是与普通对象一样的作用域,根据位置所在来判断作用域。

类的嵌套

概念:就是在类内部再定义一个类做为成员。

语法:

复制代码
class 类名1{

class 类名2
        {};
};

实例:

复制代码
class student {
public:
	class appearance {
	public:
		int height;
		int weight;
	};
private:
};
int main()
{
	student s;
	student::appearance b;
	b.height = 10;
	b.weight = 10;
	cout << b.height << b.weight << endl;
}

对象引用私有数据成员

  1. 通过公有函数的的方式进行访问和赋值

  2. 通过构造函数的方式进行赋值

  3. 利用传指针的方式获取私有数据成员

  4. 利用引用的方式获取私有数据成员

    class student {
    public:
    student(int x, int y)
    {
    this->x = x;
    this->y = y;
    }
    void setxy(int a, int b)
    {
    x = a;
    y = b;
    }
    void setxy(int* a, int* b)
    {
    *a = x;
    *b = y;
    }
    void setxy(int& a, int& b)
    {
    a = x;
    b = y;
    }
    int getx()
    {
    return x;
    }
    int gety()
    {
    return y;
    }
    private:
    int x;
    int y;
    };

成员函数重载

概念:通过类中同名不同参数类型或个数的方式实现函数重载。

复制代码
class student {
public:
	student(int x, int y)
	{
		this->x = x;
		this->y = y;
	}
	void setxy(int a)//函数重载
	{
		x = a;
		y = a;
	}
	void setxy(int* a, int* b)
	{
		*a = x;
		*b = y;
	}
	void setxy(int& a, int& b)
	{
		a = x;
		b = y;
	}
	int getx()
	{
		return x;
	}
	int gety()
	{
		return y;
	}
private:
	int x;
	int y;
};

this指针

概念:在类实例化后,对成员函数进行调用时,会自动生成一个this指针,这个this指针与类的实例化对象的地址时相同的。在类内部定义时,this指针会被隐藏,但也可以显式调用。

静态成员

概念:

类的静态数据是静态分配存储空间的,而其它成员则是动态分配空间。动态分配空间是在代码运行到类实例化后,而静态分配空间就是在编译时。静态数据成员必须文件作用域进行定义说明,默认初始值为0,静态成员不使用不分配空间。

静态成员函数

概念:在类中对函数添加static就表明这个函数时静态成员函数,它将不会包含this指针,并且在public中的成员函数可以通过不对类实例化就可以使用成员函数。使用的语法为:类名::函数名

静态成员数据变量

概念:在类声明一个静态成员变量,就表明所有类的实例化对象共用一个内存来存储这个变量,因此每个实例化对象都会影响这个变量,并且其它实例化对象会受到这影响。并且,这个静态成员变量可以在类外初始化或者是在构造函数中初始化。

复制代码
class student {
public:
	student(int x, int y)
	{
		this->x = x;
		this->y = y;
		age = 10;
	}
	void setxy(int a)//函数重载
	{
		x = a;
		y = a;
	}
	void setxy(int* a, int* b)
	{
		*a = x;
		*b = y;
	}
	void setxy(int& a, int& b)
	{
		a = x;
		b = y;
	}
	int getx()
	{
		return x;
	}
	int gety()
	{
		return y;
	}
	static void print()
	{
		cout << "hellow" << endl;
	}
	void setage(int a)
	{
		age = a;
	}
	void printage()
	{
		cout << age << endl;
	}
private:
	int x;
	int y;
	static int age;
};
int student::age = 0;
int main()
{  
	student s1(10, 10);
	s1.printage();

	
}
age此时为10

要点:

  1. 静态成员函数在类外部使用需要加上类名::,就可以直接使用
  2. 静态成员函只能直接使用本类的静态数据和静态成员函数,不能调用非静态成员,原因是静态成员函数不包含this指针
  3. 静态成员函数在类外定义时,不能加static,原因是static不是数据类型的组成部分
  4. 静态成员函数不能实现多态性,因此不能作为虚函数。

构造函数

概念:在对类实例化生成对象时,会自动调用构造函数。因此构造函数必须为公有的,如果类定义只用于派生类,可以将构造函数设置为保护成员函数,每个对象都必须调用构造函数。

语法:与类名相同,可带参数和不带参数,并且没有返回值,一个类可以有多个构造函数但需要满足函数重载的原则,并且可以对参数设置缺省值。

默认构造函数:当没有显式设置构造函数时,系统会自动分配一个构造函数。它不会对数据成员赋值,因此数据成员的值时不确定的。

调用构造函数的场景:当它时局部对象时,每次调用都会生成一个构造函数。当时全局对象时,程序只会在遇到它后调用一次构造函数,并且直到程序结束。当时静态对象的时候,首次定义对象,需要构造函数并且需要程序执行到这个静态对象的构造位置。

复制代码
#include<iostream>
using namespace std;
class student {
public:
	student(string name)
	{
		this->name = name;
		cout << "构造函数" << "名字:"<<this->name<<endl;
	}


private:
	int height;
	int width;
	string name;
};
void func()
{
	student s(string("局部对象"));
	static student s3("静态对象");

}
student s2(string("全局对象"));
int main()
{
	
	int n = 3;
	while (n--)
	{
		func();
	}

}

构造函数与new运算符

可以使用new运算符来动态地建立对象,而new的对象需要手动释放,建立时要自动调用构造函数,以便完成初始化对象的数据成员。最后返回这个动态对象的起始地址。new运算符产生的动态对象,在不再使用这种对象时,必须用****delete运算符来释放对象所占用的存储空间。 用new建立类的对象时,可以使用参数初始化动态空间。

析构函数

概念:类构造的实例对象在出生命周期就会自动调用析构函数,便于对对象内部开辟的地址进行销毁,避免内存泄漏,当类对象有手动开辟的空间,需要在析构函数中对这个空间进行释放。

语法:~加函数名,需要设置为公有函数,不能带参数和返回值,以及不直到函数类型,不允许重载。

默认析构函数:当没有显式定义析构函数时,系统会自动生成析构函数。系统就会自动收回为对象所分配的存储空间,但是不能自动收回new开辟的空间,因此需要显式定义析构函数。

复制代码
#include<iostream>
using namespace std;
class student {
public:
	student(string name)
	{
		this->name = name;
		s = new int;
		cout << "构造函数" << "名字:"<<this->name<<endl;
	}
	~student()
	{
		delete s;
		cout << "析构函数" << endl;

	}

private:
	int height;
	int width;
	string name;
	int* s;
};

void func()
{
	student s(string("局部对象"));
	static student s3("静态对象");

}
student s2(string("全局对象"));
int main()
{
	
	
	student *s=new student (string("局部对象"));
	cout << s << endl;
	delete s;
	s = nullptr;

}

拷贝构造

概念:当两个对象属于同一个类,当一个对象需要实例化,并且与另一个已经实例化的对象的数据成员一样的值,便可以通过拷贝构造,省去对数据成员赋值的步骤,提高效率。如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。

语法:classname (const classname &obj) { // 构造函数的主体}

复制代码
#include<iostream>
using namespace std;
class student {
public:
	student(string name,int a,int b)
	{
		this->name = name;
		s = new int;
		height = a;
		width = b;
		cout << "构造函数" << "名字:"<<this->name<<" height: "<<height<<" width: "<<width << endl;
	}
	~student()
	{
		delete s;
		cout << "析构函数" << endl;

	}
	student(student& obj)
	{
		height = obj.height;
		width = obj.width;
		s = new int;
		*s = *(obj.s);
		cout << "拷贝构造函数" << "名字:" << this->name << " height: " << height << " width: " << width << endl;
	}

private:
	int height;
	int width;
	string name;
	int* s;
};

//void func()
//{
//	student s(string("局部对象"));
//	static student s3("静态对象");
//
//}
//student s2(string("全局对象"));
int main()
{
	
	
	//student *s=new student (string("局部对象"));
	student s("局部对象1", 1, 2);
	student s2(s);
	


}

默认拷贝构造:可以不用显式写出拷贝构造函数,只适用于类成员没有携带指针变量并动态内存分配,因为防止浅拷贝(两个指针指向同一块区域)

复制代码
class teacher {
public:
	teacher(int a, int b)
	{
		
	
		height = a;
		width = b;
		cout << "构造函数" << " height: " << height << " width: " << width << endl;
	}
public:
	int height;
	int width;
};


int main()
{
	

	teacher t1(10, 20);
	teacher t2(t1);
	
	cout <<"t2:" << " height: " << t2.height << " width: " << t2.width << endl;
}

友元函数

概念:友元函数可以方法类里面的所有数据成员和函数成员,不受限定符限制。它不属于成员函数,但是可以访问类中的私有成员。类具有封装性和信息隐藏的特性,只要类成员和友元函数可以访问,友元可以提高程序效率,但友元会破坏这些特性。

语法:在类中声明加上friend和函数名

复制代码
class person {
	friend int func(person& p);//友元声明
public:
	person(int a, int b)
	{


		height = a;
		width = b;
		cout << "构造函数" << " height: " << height << " width: " << width << endl;
	}
private:
	int height;
	int width;
};

int func(person& p)//函数定义
{
	return p.width * p.height;
}
int main()
{

	person p(10, 20);
	cout << func(p) << endl;

}

要点:

  1. 友元函数不是类的成员函数
  2. 友元函数没有this指针,它只是一个普通类,一般需要将类对象作为参数来访问私有成员。
  3. 友元关系不能继承

友元函数与一般函数的区别

  1. 友元函数可以定义在类中或者类外,普通函数只能定义在类外
  2. 友元函数可以访问类的所有成员,普通函数只能访问公有成员

友元类

概念:一个类作为另一个类的友元,意味着这个类可以访问另一个类的所有成员。

语法:在类中声明friend class 类名(友元类的类名)

复制代码
class person {
	friend int func(person& p);
	friend class child;
public:
	person(int a, int b)
	{


		height = a;
		width = b;
		cout << "构造函数" << " height: " << height << " width: " << width << endl;
	}
private:
	int height;
	int width;
};
class child {

public:
	int getheigtandwidth(person& p)
	{
		return p.height * p.width;
	}
};

int func(person& p)
{
	return p.width * p.height;
}
int main()
{

	person p(10, 20);
	child c;

	cout << func(p) << endl;
	cout << c.getheigtandwidth(p) << endl;
}

要点:

  1. 友元关系不能继承
  2. 友元关系是单向的,比如b是a的友元,b能够使用a的所有成员,a不能使用b的所有成员
  3. 友元关系不具有传递性,比如b是a的友元,a是c的友元,b不是c的友元。

动态内存分配

概念:在定义变量或数组时,可以手动为其分配内存,一般用于在程序开始之后根据需要开指定大小的空间,以避免直接在栈上创建对象,消耗多余的内存。也用于需要非常长的生命周期或很大内存空间的场景。

语法:内存分配(malloc/new) 销毁内存(free/delete)

要点:

  1. 内存泄漏问题,开空间后必须要销毁,如果不销毁,这快空间只要重新开机才能使用
  2. 野指针的问题,为对指向销毁内存的指针进行指向空,防止对空的地址使用。
  3. 重复释放问题,因为害怕后面要使用销毁的那块地址,所有要对指针悬空。

c++中new和delete的用法

复制代码
#include<iostream>
using namespace std;
int main()
{
	int* p1 = new int(10);//分配一个int类型对象,不初始化
	int* p2 = new int[10];//分配十个int的数组
	delete p1;//销毁一个int类型对象
	delete[]p2;//销毁一个int数组

}

运算符重载

概念:在c++类中,可以针对类进行使用运算符,而且还可以对运算符进行重载,一个运算符可以表达多个含义,目的是为了解决类内部需要开空间的问题和返回值的问题。

语法:返回类型 operator运算符(参数列表) { // 实现 }

复制代码
#include<iostream>
using namespace std;
class student {
public:
	student(int a, int b)
	{
		age = a;
		height = b;
	}
	student operator+(const student& other)//算数运算符重载
	{
		return student(this->age + other.age, this->height + other.height);
	}
	bool operator==(const student& other)//关系运算符重载
	{
		return this->age == other.age && this->height == other.height;
	}
	student operator=(const student& other)//赋值运算符重载
	{
		if (this != &other) {  // 防止自赋值
			this->age = other.age;
			this->height = other.height;
		}
		return *this;  // 返回当前对象的引用
	}
	friend ostream& operator<<(ostream& os, const student& s)
	{
		os << "Age: " << s.age << ", Height: " << s.height;
		return os;
	}
	student& operator++()//前置++
	{
		this->age++;
		this->height++;
		return *this;
	}
	student operator++(int)//后置++
	{
		student p(this->age, this->height);
		this->age++;
		this->height++;
		return p;
	}


private:
	int age;
	int height;
};
int main() {
	student s1(20, 170);
	student s2(22, 175);

	// 算术运算
	student s3 = s1 + s2;
	cout << s3 << endl;  // 输出: Age: 42, Height: 345

	// 关系运算
	cout << (s1 == s2) << endl;  // 输出: 0 (false)

	// 赋值运算
	student s4(0,0);
	s4 = s1;
	cout << s4 << endl;  // 输出: Age: 20, Height: 170

	// 递增运算
	++s1;
	cout << s1 << endl;  // 输出: Age: 21, Height: 171

	s2++;
	cout << s2 << endl;  // 输出: Age: 23, Height: 176

	return 0;
}

继承

概念:继承性是面向对象的一种机制,目的是在原有的基础上进行扩展和完善,从而节省重新程序开发的时间,并且节省资源。

单继承

概念:就是在原有的类上,添加一些新的内容再建立一个类,并且可以使用原有类的成员。其中原来的类被称为基类(父类),继承基类的类被称为派生类(子类)。并且只继承一个类。

此时父类的属性会被子类继承,子类可以使用父类的公有成员和保护成员,并且会生成父类。

派生类的作用:

  1. 可以使用基类的成员数据和成员函数
  2. 可以增加新的成员
  3. 可以重新定义已有的成员函数
  4. 可以改变现有的成员属性

派生方式:

1.公有派生方式:

子类可以访问派生类的私有成员和公有成员,但不能访问私有成员,其中公有成员始终保持公有成员的性质(可以在类外面调用),保护对象也保留保护的性质

2.保护派生方式:

子类可以访问派生类的私有成员和公有成员,但不能访问私有成员,其中公有成员和保护成员都变为保留保护性质。

复制代码
#include<iostream>
using namespace std;
class call {
public:
	call()
	{

	}
	call(int Age)
	{
		age = Age;
		cout << "动物叫" << endl;
	}

public:
	int age;//父类成员
protected:
	string name;
	
};
class dogcall : protected call
{
public:
	dogcall()
	{

	}
	dogcall(int a,int b):call(a){
		height = 10;
		name = "dog";
		cout << "狗叫" << endl;
	}
public:
	int height;
};
class D :public dogcall
{
public:
	D()
	{   age=10;
		name = 10;
		cout << "D()" << endl;
	}
};
int main()
{
	dogcall d(2,10);
	cout << "动物高度" << d.height << endl;
	D s;
}

2.私有派生方式:

子类不能访问父类的私有成员,而父类的公有和保护成员都变为子类的私有成员。

复制代码
#include<iostream>
using namespace std;
class call {
public:
	call()
	{

	}
	call(int Age)
	{
		age = Age;
		cout << "动物叫" << endl;
	}

public:
	int age;//父类成员
protected:
	string name;
	
};
class dogcall : private call
{
public:
	dogcall()
	{

	}
	dogcall(int a,int b):call(a){
		height = 10;
		name = "dog";
		cout << "狗叫" << endl;
	}
public:
	int height;
};

int main()
{
	dogcall d(2,10);
	cout << "动物高度" << d.height << endl;
	
}

多继承

概念:一个派生类继承多个基类,这个派生类便可以根据继承类型来访问对应的基类成员。

复制代码
class A {
public:
	A()
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
};

class B {
public:
	B()
	{
		cout << "B()" << endl;
	}
	~B()
	{
		cout << "~B()" << endl;
	}
};

class C {
public:
	C()
	{
		cout << "C()" << endl;
	}
	~C()
	{
		cout << "~C()" << endl;
	}
};
class D:public A,B,C 
{
public:
	D()
	{
		cout << "D()" << endl;
	}

	~D()
	{
		cout << "~D()" << endl;
	}
private:
	A a;

};
int main()
{
	D d;
}

特点:创建先从基类开始,然后到成员对象,再到自身对象创建。销毁则是显式自身,再是成员,最好父类对象。基类的构造函数 子对象类的构造函数 派生类的构造函数

抽象类

概念:定义一个类,这个类的所有成员都不进行定义,而只是为了让派生类去定义属于派生类的这个成员,而且这个类的所有成员,派生类必须实现,并且这个类的构造函数或析构函数的访问权限定义为保护。表明基类只能通过派生类创建,无法在类外实例化。

复制代码
#include<iostream>
using namespace std;
class call {
public:
	
protected:
	call(int Age)
	{
		age = Age;
		cout << "动物叫" << endl;
	}

public:
	int age;//父类成员
protected:
	string name;
	
};
class dogcall : private call
{
public:

	dogcall(int a,int b):call(a){
		height = 10;
		name = "dog";
		cout << "狗叫" << endl;
	}
public:
	int height;
};

int main()
{
	dogcall d(2,10);
	cout << "动物高度" << d.height << endl;
	
}

继承冲突问题

概念:为了解决多继承时,遇到基类们的成员名相同的情况。

复制代码
class A {
public:
	A()
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
	int x;
};

class B {
public:
	B()
	{
		cout << "B()" << endl;
	}
	~B()
	{
		cout << "~B()" << endl;
	}
	int x;
};

class C {
public:
	C()
	{
		cout << "C()" << endl;
	}
	~C()
	{
		cout << "~C()" << endl;
	}
	int x;
};
class D:public A,public B, public C 
{
public:
	D()
	{
		cout << "D()" << endl;
	}

	~D()
	{
		cout << "~D()" << endl;
	}
private:
	A a;

};

多继承访问相同名字的成员时,就不知道要访问能够基类的成员,如果是在派生类新加的同盟成员时,不加限制,优先调用派生类的成员。

只有通过这种方式进行特定使用

基类和对象成员类的区别

  1. 如果基类是多个,遇见基类名相同就会触发冲突。
  2. 而在类中创建类成员来访问相同名的成员就不会触发冲突

赋值兼容性

概念:子类对象可以直接赋值给基类对象,但是基类对象不能直接赋值给子类对象。

复制代码
class C {
public:
	C()
	{
		cout << "C()" << endl;
	}
	~C()
	{
		cout << "~C()" << endl;
	}
	int x;
};
class D :  public virtual C
{
public:
	D(int a,int b)
	{
		y = a;
		x = b;
		cout << "D()" << endl;
	}

	~D()
	{
		cout << "~D()" << endl;
	}
private:
	int y;

};
int main()
{
	D d(10,20);
	C c;
	c = d;
	cout << c.x << endl;


}

此时c.x的结果为20。

派生类对象的地址赋给基类的指针变量

作用:通过一个统一接口,来操作不同的派生类对象,从而实现"一个接口,多种方法"

  1. 结合虚函数构成多态,通过结合虚函数 。当基类中的函数被声明为 virtual 时,通过基类指针调用该函数,程序会在运行时根据指针实际指向的对象的类型(而不是指针本身的类型)来决定调用哪个版本的函数。
  2. 实现统一的接口和代码复用
  3. 可以用同一个容器存放不同类型的对象

派生类对象可以初始基类的引用

作用:与地址赋指针的功能一样。

不同之处:

特性 基类指针 基类引用
语法 Base* ptr = &derived; Base& ref = derived;
可为空 可以设置为 nullptr 必须绑定到有效对象
重新绑定 可以指向其他对象 一旦初始化就不能改变绑定
内存管理 需要关注所有权和释放 自动管理生命周期
安全性 需要检查空指针 更安全,总是指向有效对象
复制代码
// 基类:形状
class Shape {
public:
    // 虚函数
    virtual void draw() const {
        std::cout << "Drawing a generic shape." << std::endl;
    }

    // 虚析构函数至关重要(后面会讲)
    virtual ~Shape() = default;
};
// 派生类:圆形
class Circle : public Shape {
public:
    // 重写基类的虚函数
    void draw() const override {
        std::cout << "Drawing a circle." << std::endl;
    }
};

// 派生类:矩形
class Rectangle : public Shape {
public:
    // 重写基类的虚函数
    void draw() const override {
        std::cout << "Drawing a rectangle." << std::endl;
    }
};

int main() {
    // 创建派生类对象,但用基类指针指向它们
    Shape* shape1 = new Circle();
    Shape* shape2 = new Rectangle();

  
    // 同一个接口(shape->draw()),不同的行为
    shape1->draw(); // 输出:Drawing a circle.
    shape2->draw(); // 输出:Drawing a rectangle.

    // 甚至可以放在一个数组里统一处理
    Shape* shapes[] = { shape1, shape2 };
    for (int i = 0; i < 2; ++i) {
        shapes[i]->draw(); // 运行时决定调用哪个draw
    }

    delete shape1;
    delete shape2;
    return 0;
}

虚基类

概念:当B、C都继承统A类时,然后再有一个D类同时继承B/C类时,进行实例化,就会创建两个A类对象,就会多开辟一个空间,并且会造成多个拷贝中的数据不一致和模糊引用。而虚基类就可以只开一个A类对象,而B、C 共用一块A类对象和资源,就可以节省资源,使用A类成员时,不用指定继承类标识,因为它们都是一块资源。

复制代码
class A {
public:
	A()
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
	int x;
};

class B:public  A
{
public:
	B()
	{
		cout << "B()" << endl;
	}
	~B()
	{
		cout << "~B()" << endl;
	}
	
};

class C :public  A 
{
public:
	C()
	{
		cout << "C()" << endl;
	}
	~C()
	{
		cout << "~C()" << endl;
	}
	
};
class D :  public C ,public  B
{
public:
	D(int a,int b)
	{
		y = a;
		
		
		cout << "D()" << endl;
	}

	~D()
	{
		cout << "~D()" << endl;
	}
private:
	int y;

};
int main()
{
	D d(10,20);
	
	


}

此时会构建两个A类对象,原因C和B都继承了它,所以都要为A类开空间

复制代码
class A {
public:
	A()
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
	int x;
};

class B:public virtual A
{
public:
	B()
	{
		cout << "B()" << endl;
	}
	~B()
	{
		cout << "~B()" << endl;
	}
	
};

class C :public virtual A 
{
public:
	C()
	{
		cout << "C()" << endl;
	}
	~C()
	{
		cout << "~C()" << endl;
	}
	
};
class D :  public C ,public  B
{
public:
	D(int a,int b)
	{
		y = a;
		x = b;
		
		cout << "D()" << endl;
	}

	~D()
	{
		cout << "~D()" << endl;
	}
private:
	int y;

};
int main()
{
	D d(10,20);
	cout << d.x << endl;//不用指定类就可以访问A类成员
	


}

此时就少开了一个A类对象空间,并且可以直接访问A类成员

作用

  1. 避免重复构造
  2. 确保单一实例

注意事项

1.调用顺序:虚基类构造函数、非虚基类构造函数、成员对象构造函数、派生类构造函数

2.如果虚基类没有显式写默认构造函数,并且写了一个新的构造函数,此时就需要在非虚基类构造函数中显式构造虚基类对象。

复制代码
class A {
public:
	A(int a)
	{
		x = a;
	}
	/*A()
	{
		cout << "A()" << endl;
	}*/
	~A()
	{
		cout << "~A()" << endl;
	}
	int x;
};

class B:public virtual A
{
public:
	B():A(10)
	{
		cout << "B()" << endl;
	}
	~B()
	{
		cout << "~B()" << endl;
	}
	
};

class C :public virtual A 
{
public:
	C() :A(10)
	{
		cout << "C()" << endl;
	}
	~C()
	{
		cout << "~C()" << endl;
	}
	
};
class D :  public C ,public  B
{
public:
	D(int a,int b):A(b)
	{
		y = a;
		
		
		cout << "D()" << endl;
	}

	~D()
	{
		cout << "~D()" << endl;
	}
private:
	int y;

};
int main()
{
	D d(10,20);
	cout << d.x << endl;//不用指定类就可以访问A类成员
	


}

虚函数

概念:当基类的函数被声明为虚函数时,派生类中重写该函数就可以实现多态。这意味着通过基类指针或引用时就会调用派生类的该函数,将根据实际对象的类型来调用相应函数。这样就可以通过统一接口调用不同的功能。

这是为虚函数的场景

复制代码
class Shape {
public:
    // 虚函数
    virtual void draw() const {
        std::cout << "Drawing a generic shape." << std::endl;
    }

    // 虚析构函数至关重要(后面会讲)
    virtual ~Shape() = default;
};
//
// 派生类:圆形
class Circle : public Shape {
public:
    // 重写基类的虚函数
    void draw() const override {
        std::cout << "Drawing a circle." << std::endl;
    }
};

// 派生类:矩形
class Rectangle : public Shape {
public:
    // 重写基类的虚函数
    void draw() const override {
        std::cout << "Drawing a rectangle." << std::endl;
    }
};

int main() {
    // 创建派生类对象,但用基类指针指向它们
    Shape* shape1 = new Circle();
    Shape* shape2 = new Rectangle();

  
    // 同一个接口(shape->draw()),不同的行为
    shape1->draw(); // 输出:Drawing a circle.
    shape2->draw(); // 输出:Drawing a rectangle.

    // 甚至可以放在一个数组里统一处理
    //Shape* shapes[] = { shape1, shape2 };
    //for (int i = 0; i < 2; ++i) {
    //    shapes[i]->draw(); // 运行时决定调用哪个draw
    //}

    delete shape1;
    delete shape2;
    return 0;
}

这是不为虚函数的场景

复制代码
 //基类:形状
class Shape {
public:
    // 虚函数
     void draw() const {
        std::cout << "Drawing a generic shape." << std::endl;
    }

    // 虚析构函数至关重要(后面会讲)
    virtual ~Shape() = default;
};
//
// 派生类:圆形
class Circle : public Shape {
public:
    // 重写基类的虚函数
    void draw() const {
        std::cout << "Drawing a circle." << std::endl;
    }
};

// 派生类:矩形
class Rectangle : public Shape {
public:
    // 重写基类的虚函数
    void draw() const {
        std::cout << "Drawing a rectangle." << std::endl;
    }
};

int main() {
    // 创建派生类对象,但用基类指针指向它们
    Shape* shape1 = new Circle();
    Shape* shape2 = new Rectangle();

  
    // 同一个接口(shape->draw()),不同的行为
    shape1->draw(); // 输出:"Drawing a generic shape."
    shape2->draw(); // 输出:"Drawing a generic shape."

  

    delete shape1;
    delete shape2;
    return 0;
}
特性 虚函数重写 (Virtual Override) 非虚函数"重写" (实际是隐藏)
多态性 支持运行时多态 不支持多态
函数绑定 动态绑定(运行时) 静态绑定(编译时)
调用决定 由对象实际类型决定 由指针/引用类型决定
关键字 需要 virtualoverride 不需要特殊关键字
设计意图 明确设计为可扩展的接口 意外行为,通常应该避免

为什么要添加虚析构函数

核心原因:
  1. 多态安全:确保通过基类指针删除派生类对象时,派生类的析构函数被调用

  2. 资源管理:避免内存泄漏、资源泄漏

  3. 符合RAII:确保所有资源在对象生命周期结束时正确释放

虚函数的关键字

override关键字

概念:只有是虚函数,才能添加这个关键字,否者会报错

final关键字

概念:表明这个函数禁止被继承类重写

抽象类

概念:一个类的成员没有具体实现,需要通过虚函数和继承的方式由派生类定义。而抽象类就是指带有纯虚函数的类。

语法:virtual 函数类型 函数名**(参数表)****=**0;

当为纯虚函数就无法构成对象,只能作为基类构成多态

复制代码
class Shape {
public:
    // 虚函数
    virtual void draw() const = 0;

    virtual ~Shape() = default;
};
//
// 派生类:圆形
class Circle : public Shape {
public:
    // 重写基类的虚函数
    void draw() const  {
        std::cout << "Drawing a circle." << std::endl;
    }
};


// 派生类:矩形
class Rectangle : public Shape {
public:
    // 重写基类的虚函数
    void draw() const {
        std::cout << "Drawing a rectangle." << std::endl;
    }
};

int main() {
    Shape *shape1=new Circle();
    Shape* shape2=new Rectangle();
    shape1->draw();
    shape2->draw();
   
   
    return 0;
}

命名空间

概念:命名空间是一个强大的工具,它不仅仅是让你"偷懒"不用写前缀。它的本质是工程化管理代码,划分逻辑边界,防止命名污染

using namesapce:解决不用在函数前加命名空间名

自定义命名空间:namespace 名称{内容}

命名空间的作用

解决名称冲突,避免函数、类、变量等标识符的名称冲突

复制代码
// 第三方网络库提供的功能
void connect() {
    std::cout << "Connecting to network...\n";
}

// 你自己的数据库功能
void connect() { // 错误!重定义 'void connect()'
    std::cout << "Connecting to database...\n";
}

int main() {
    connect(); // 编译器不知道该调用哪个
    return 0;
}

这是没有使用命名空间调用方法,就无确定是哪一个,就会起冲突。

这个就体现了命名空间的优势

命名空间的使用方式

  1. 通过命名空间名称+::+对象名进行调用空间中指定对象
  2. 在文件中使用using namespace 命名空间名,这样就可以实现本地化,不用命名空间名就可以调用空间内部的对象
  3. using 命名空间名+::+空间内部的对象,表明这个作用域下就在使用这个对象就不用显式的写出命名空间名。

名字空间的嵌套:一个命名空间内部定义另一个命名空间。使用方式:using 外部命名空间名::内部空间名

复制代码
#include<iostream>
using namespace std;

namespace A {
	void say()
	{
		cout << "hellow A" << endl;
	}
	namespace C{
		void say()
		{
			cout << "hellow C" << endl;
		}
	}
}
namespace B {

	void say()
	{
		cout << "hellow B" << endl;
	}
}
int main()
{

	A::C::say();//命名空间嵌套

}

命名空间取别名:给命名空间的名字在当前作用域设置一个简单的名字

复制代码
namespace newA = A;
int main()
{

	newA::C::say();//命名空间嵌套

}

模板

概念:模板式泛型编程的基础,泛型编程以一种独立于任何特定类型的方式编写代码。

函数模板

语法:template<typename 类型> 函数实现

调用函数模板:直接传数,不考虑类型,因为函数模板会根据传的参数自适应类型

复制代码
#include<iostream>
using namespace std;

template<typename T> 
T Max(T x,T y)
{
	return x > y ? x : y;
}
int main()
{
	int a = 10;
	int b = 20;
	cout << Max(a, b) << endl;
	string c = "s";
	string d = "e";
	cout << Max(c, d) << endl;
}

类模板

概念:template<typename 类型> class class-name{}

用类模板定义对象,T会被参数的类型替换 类名<参数类型> 对象名

复制代码
template <typename T>
class student {
public:
	student(T x, T y)
	{
		height = x;
		width = y;
	}
	T sum()
	{
		return height + width;
	}
private:
	T height;
	T width;
};
int main()
{
	student<int> s(180, 120);
	cout << s.sum() << endl;
}

IO流类库

概念:IO库的成员可以实现对外设的访问,与交互。编译系统已经通过运算符或函数的形式做好了标准外设(键盘、屏幕、打印机、文件)的接口,使用时只需要调用相关接口即可。

标准输入输出流

概念:c++语言的I/O系统为用户提供了一个统一接口,使得程序设计尽量与所访问的具体设备无关,在用户与设备直接提供了一个抽象的界面。

头文件:iostream

输入流:可以将外设的数据输入到程序中

输出流:可以将程序中的数据输出到外设中

重载输入输出运算符

重载输出

复制代码
#include<iostream>
#include<fstream>
using namespace std;

class A {
public:
	A(int  a, int b)
	{
		x = a;
		y = b;
	}
	friend ostream& operator<<(ostream& os, A&);

private:
	int x;
	int y;
};
 ostream& operator<<(ostream& os, A&other)
{  
	 cout << other.x << "\n" << other.y << endl;
	 return os;
 }
 int main()
 {
	 A a(100, 200);
	 cout << a;
	 A b(300, 400);
	 cout << a << b;//<<重载会返回输出流,因此可以直接调用<<来输出
 }

重载输入,变化格式由变量格式决定的,对输出流,将数据变换字符串然后输出

复制代码
#include<iostream>
#include<fstream>
using namespace std;

class A {
public:
	A(int  a, int b)
	{
		x = a;
		y = b;
	}
	friend ostream& operator<<(ostream& os, A&);//输出到外设
	friend istream& operator>>(istream& is, A& other);//输入到程序

private:
	int x;
	int y;
};
 ostream& operator<<(ostream& os, A&other)
{  
	 cout << other.x << "\n" << other.y << endl;
	 return os;
 }
 istream& operator>>(istream& is, A& other)
 {
	 cin >> other.x >> other.y;
	 return is;
 }
 int main()
 {
	 //A a(100, 200);
	 //cout << a;
	 //A b(300, 400);
	 //cout << a << b;//<<重载会返回输出流,因此可以直接调用<<来输出

	 A c(100, 200);
	 cin >> c;
	 cout << c;
 }

重载输出的语法:friend ostream&operator<<(ostream&,ClassName&)

重载输入的语法:friend istream&operator<<(istream&,ClassName&)

文件流类体系

将数据输出到文件和从文件输入到程序。

文件操作:文本文件、二进制文件。

文件流类

头文件<fstream>

ifstream :读取文件数据

ofstream:向文件写入

fstream:可写可读

fstream的常用成员:

fstream 文件描述符(文件名,打开方式):文件流与文件建立关联

write(字符数组的起始位置,字符长度)

复制代码
int main()
{
	fstream fd("demo.txt",ios::out);//往文件中写入

	if (fd.is_open())
	{
		cout << "文件已经打开" << endl;
	}
	else
	{
		cout << "文件打开失败" << endl;
		return -1;
	}
	const char* buffer = "hellow world";
	fd.write(buffer, strlen(buffer));//sizeof是指针长度,strlen用与字符串长度
	fd.close();
}

read(字符数组缓冲区,内容长度):可以从文件中读指定长度的字符到字符数组中,不会自动添加字符串结束符 \0 而cout << buffer 会一直输出直到遇到 \0

复制代码
fstream fd("demo.txt", ios::in);//往文件中读取

if (fd.is_open())
{
	cout << "文件已经打开" << endl;
}
else
{
	cout << "文件打开失败" << endl;
	return -1;
}
char buffer[13];

if (fd.read(buffer, 12))//sizeof是指针长度,strlen用与字符串长度,这个缺点是需要直到文件的字符长度,无法高效读取所有数据
{
	buffer[fd.gcount()] = '\0';//gcount获取读取的字符数
	cout << buffer << endl;
	cout << fd.gcount() << endl;
}
	fd.close();

get(字符缓冲区):把文件的字符一一读取到字符缓冲区中,直到读到文件末尾(其中有一个读取指针,会往后移动)

复制代码
string buffer;
fstream fd("demo.txt", ios::in);//往文件中读取

if (fd.is_open())
{
	cout << "文件已经打开" << endl;
}
else
{
	cout << "文件打开失败" << endl;
	return -1;
}
char s;
while (fd.get(s))//循环读取文件的数据,到达末尾或遇到EOF
{
	buffer += s;
}
cout << buffer.c_str() << endl;
fd.close();

put成员方法:输入单个字符到文件,与cin结合,根据键盘输入来一个一个字符输入到文件中

实际上,按回车并不能直接停止输入 。要停止从键盘输入,需要使用 文件结束符(EOF)

Windows系统:

Ctrl + Z 然后按回车

Linux/Mac系统:

Ctrl + D

文件指针:当文件打开时,文件指针位于开头,并随读写字节数的多少顺序移动,可以利用seekg移动文件指针

复制代码
fstream fd("demo.txt", ios::in);//往文件末尾进行添加新的数据

if (fd.is_open())
{
	cout << "文件已经打开" << endl;
}
else
{
	cout << "文件打开失败" << endl;
	return -1;
}
string buffer;
string content;
while (getline(fd, buffer));//循环读取每行,直到读取到0个字符
{
	content += buffer + '\n';
}
fd.close();
cout << content << endl;

getline:属于c++的风格,按行读取内容,读取到文本末尾自动返回0。

文件打开方式标记

标记|标记:表明两个方式都可以实现

STL

概念:c++的一部分不需要额外库,

vector

序列式的容器(普通数组的升级版),属于一个动态数组(根据插入的数据开辟空间,不用提前规定数组的大小),可以对元素进行插入和删除,并且可以放各种类型的数据。

初始化方法

1.不加数据创建:

复制代码
vector<int> v;

2.加上数据创建

复制代码
	vector<int> v2{ 10,20,30 };

3.直接设置元素个数:先只开辟2个元素的空间,设置初始包含几个元素,并且每个元素都默认为0

复制代码
	vector<int> v3(2);

4.设置多个相同的数据

复制代码
	vector<int> v4(3, 10);

5.复制,容器间赋值

复制代码
	vector<int>v5(v2);

6.保存指定数组的数据:如果访问超出v6存放arr的值的范围会越界

复制代码
int arr[] = { 10,2,13 };
vector<int>v6(arr, arr+1);

7.保存vector中的指定数据

复制代码
	vector<int> v7(v2.begin() + 1, v2.end());

begin:返回第一元素的迭代器

end:最后一个元素的后一位置的迭代器

size:返回元素个数

push_back:插入元素

insert:在指定位置插入数据(位置,数据)

at():获取对应下标的数据

\[\]:获取对应下标的数据

遍历容器的方法

复制代码
	vector<int> v;
	v.push_back(1);
	v.push_back(3);
	v.push_back(5);
	for (auto e : v)
	{
		cout << e << endl;
	}
	for (int i = 0; i < v.size(); i++)
	{
		cout << v[i] << endl;
	}

deque:双端队列容器

与vector:擅长在序列头部和尾部插入,时间复杂度比vector少

初始化:与vector大部分相同,

push_back:在后面插入

size

pop_front():删除队列头部的值

复制代码
	deque<int> d;
	d.push_back(10);
	d.push_front(5);
	d.push_front(20);
	d.push_front(15);
	d.pop_back();
	d.pop_front();
	for (auto e : d)
	{
		cout << e<<" ";
	}

stack:堆栈

概念:堆栈是一种容器适配器,专门设计用于在后进先出环境(后进先出)中运行,其中元素仅从容器的一端插入和提取。后进先出类似于放一堆书一样,只有一一从书顶部取出书,放置也只能放在顶部。容器适配器是使用特定容器类的封装对象作为其底层容器的类,提供一组特定的成员函数来访问其元素。

要点:stack本身不是一个完整的容器,而是一个容器适配器。在大多数情况下,使用默认的stack<int>就足够了。只有当你有特定性能需求或特殊使用场景时,才需要考虑指定底层容器

考虑指定容器的情况

  • 明确知道元素数量且很大 → vector + reserve()

  • 元素很大,复制成本高 → list

  • 需要内存连续性 → vector

  • 基准测试显示特定容器有明显优势

  • push:先构造临时对象,再拷贝/移动到容器

  • emplace:直接在容器中构造对象

    class Data {
    public:
    int x;
    double y;

    复制代码
      // 构造函数
      Data(int a, double b) : x(a), y(b) {
          std::cout << "构造 Data(" << x << ", " << y << ")" << std::endl;
      }
    
      // 拷贝构造函数
      Data(const Data& other) : x(other.x), y(other.y) {
          std::cout << "拷贝构造 Data(" << x << ", " << y << ")" << std::endl;
      }
    
      // 移动构造函数
      Data(Data&& other) noexcept : x(other.x), y(other.y) //剥夺other的资源,包括指针,全部给返回值,扩大生命周期,移动后原对象不再拥有资源
      {
          std::cout << "移动构造 Data(" << x << ", " << y << ")" << std::endl;
      }
    
      // 析构函数
      ~Data() {
          std::cout << "析构 Data(" << x << ", " << y << ")" << std::endl;
      }

    };
    int main() {
    std::cout << "=== 使用 push ===" << std::endl;
    {
    std::stack s1;
    s1.push(Data(1, 2.0)); // 先构造临时对象,再移动(如果支持)到栈中
    }

    复制代码
      std::cout << "\n=== 使用 emplace ===" << std::endl;
      {
          std::stack<Data> s2;
          s2.emplace(1, 2.0); // 直接在栈内存中构造对象
      }
    
      return 0;

    }

queue 双端队列

概念:队列是一种容器适配器,专门设计用于在 FIFO 上下文(先进先出)中运行,其中元素插入容器的一端并从另一端提取。

back():访问最后一个元素

push:在后面插入

pop:删除第一个元素

front:访问第一个元素

size:访问长度

emplace:末尾添加

swap:两个queue的内容交换

复制代码
#include <iostream>       // std::cout
#include <queue>          // std::queue

int main()
{
	std::queue<int> myqueue;

	myqueue.push(12);
	myqueue.push(75);   // this is now the back

	//myqueue.back() -= myqueue.front();

	std::cout << "myqueue.back() is now " << myqueue.back() << '\n';//75

	myqueue.back() -= myqueue.front();
	std::cout << "myqueue.back() is now " << myqueue.back() << '\n';//63
	while (!myqueue.empty())
	{
		std::cout << myqueue.front() << std::endl;
		myqueue.pop();
	}

	return 0;
}

set/map

概念:map具有两个值,一个为键,一个为值,每个键对应一个值,元素是无序的。set具有一个值,元素是有序的。unordered_map和unordered_set是上面两个的另一种形式,它们是无序的,仅仅为了快速的查找。

组成:

键(key):唯一标识符,用于快速排序和查找

值(value):于键相关联的数据。

特性:

键是唯一的情况(map),重复的情况(multimap)。

键不可修改(若需要修改需要先删除再插入新的键值对)

值可以随意修改。

底层实现:

pair和make_pair是用于对创建和操作键值对的核心工具。

pair是std的模板类,用于将两个值(键和值)组合称为一个对象。

make_pair是一个模板函数,用于自动推导类型并生成pair对象,避免显示指定模板参数

函数原型:

自动类型推导:

插入容器(make_pair和pair的区别):

map

存储键值对(key-value pairs)

复制代码
#include<iostream>
#include<map>
using namespace std;
int main()
{
	map<string, int> m;//默认按键排序
	m.insert(make_pair("apple", 8));//insert要带类型才能插入,除非用make_pair
	m.insert(make_pair("banana", 2));
	m.insert(pair<string, int>("cherr", 7));

	for (auto it = m.begin(); it != m.end(); it++)//正向迭代器
	{
		cout << it->first << ":" << it->second << endl;
	}
}

复制代码
int main()
{
	map<string, int,greater<string>> m;//按降序排序
	m.insert(make_pair("apple", 8));//insert要带类型才能插入,除非用make_pair
	m.insert(make_pair("banana", 2));
	m.insert(pair<string, int>("cherr", 7));

	for (auto it = m.begin(); it != m.end(); it++)//正向迭代器
	{
		cout << it->first << ":" << it->second << endl;
	}
	cout << "反向迭代器:" << endl;
	for (auto it = m.rbegin(); it != m.rend(); it++)//反向迭代器
	{
		cout << it->first << ":" << it->second << endl;
	}
}

复制代码
	m.erase("apple");//根据键做出删除操作
	for (auto it = m.begin(); it != m.end(); it++)//正向迭代器
	{
		cout << it->first << ":" << it->second << endl;

复制代码
for (auto it = m.begin(); it != m.end(); it++)//正向迭代器
{
	cout << m[it->first]<< endl;//根据键访问值
}

set

特性:只存储键(key),不存储值(value),自动去重功能,自动排序。

复制代码
#include<iostream>
#include<set>
using namespace std;
int main()
{
	set<int> s{ 67,78,3,90,55,55,66,77,77 };//自动去重
	s.insert(100);//插入元素
	s.erase(1);//删除元素
    //查找元素
	auto it = s.find(3);//返回这个数的迭代器
	if (it != s.end())
	{
		cout << "找到元素" <<*it<< endl;
	}

	// 方法1:范围for循环
	for (const auto& e : s) {
		std::cout << e << " ";
	}
	std::cout << std::endl;

	// 方法2:迭代器
	for (auto it = s.begin(); it != s.end(); ++it) {
		std::cout << *it << " ";
	}
	std::cout << std::endl;

	// 方法3:反向迭代器
	for (auto rit = s.rbegin(); rit != s.rend(); ++rit) {
		std::cout << *rit << " "; // 逆序输出
	}

}

c++11的unordered_set/unordered_map

unordered_set:不会进行排序,而是按插入的顺序进行排列,因此时间复杂度会更少。

unordered_map:底层采用hash表结构,具备快速检索的功能,没有顺序,键值唯一,动态管理空间。

异常处理

程序中常见的错误有两大类:语法错误和运行错误。在编译时,编译系统能发现程序中的语法错误

常见的编译错误:

  1. 语法错误
  2. 类型错误
  3. 声明错误
  4. 链接错误

运行时错误:

  1. 内存访问错误:空指针引用、野指针、越界、使用已经释放的内存
  2. 资源管理错误:内存泄漏
  3. 逻辑错误:死循环、除零
  4. 异常:自定义的错误、动态分配失败

区别:

方面 编译错误 运行时错误
检测时间 编译时 运行时
错误示例 语法错误、类型错误、链接错误 内存访问错误、逻辑错误、资源错误
调试工具 编译器错误信息 调试器、Valgrind、ASan
预防方法 代码审查、静态分析 测试、断言、异常处理
影响范围 整个程序无法运行 程序可能部分运行或崩溃
修复难度 通常较容易定位 可能难以重现和定位

概念:异常处理是运行程序遇到运行时错误时,将控制权从发生错误的部分转移到专门处理错误代码的部分。

throw:当问题出现,程序抛出异常

catch:捕捉异常

try:放置在可能会抛出异常的地方,然后立即进行处理

c++语言通过throw语句和try...catch实现对异常处理

throw 表达式;

此语句抛出一个异常,异常是一个表达式,其值的类型可以是基本类型,也可以是类

作用:对特定错误进行捕捉,而不是报错,捕捉到异常后可以对异常做出相应的处理

语法:

try{语句组}

catch(异常类型)

{异常处理代码}

标准异常类exception

bad_typeid(对空指针的引用) bad_cast(强制类型转换的错误) bad_alloc(开空间,空间不够 ios_base::failure out_of_range(越界)

复制代码
#include<iostream>
#include<stdexcept>
#include<string>
double divide(double a, double b) {
    if (b == 0) {
        throw std::runtime_error("除数不能为零");
    }
    return a / b;
}
int main()
{
    try {
        double result = divide(10, 0);
        std::cout << "结果: " << result << std::endl;
      }
    catch(const std::runtime_error& e)
    {
        std::cout << "捕获到运行时错误: " << e.what() << std::endl;
    }
}

c++语言标准特性

类型推导

概念:类型推导是现代C++中非常重要的特性,它让编译器能够自动推断变量或表达式的类型。

auto 变量=值:自动为变量推导类型

复制代码
int main()
{
	auto x = 5;//推导出int
	cout << sizeof(&x) << x<<endl;//32位下4字节,并且得到值

	int x = 10;
	const int cx = x;
	const int& rx = x;

	auto a1 = cx;//忽略const
	a1 = 11;
	auto a2 = rx;//忽略const和引用
    // 规则2: 保留底层const
    const int* ptr = &x;
    auto a3 = ptr;       // const int* (保留底层const)

    // 规则3: auto& 会保留const和引用
    auto& a4 = cx;       // const int&
    auto& a5 = rx;       // const int&

    // 规则4: auto&& 万能引用
    auto&& a6 = x;       // int&
    auto&& a7 = cx;      // const int&
    auto&& a8 = 42;      // int&&

}

decltype(表达式)变量名称=值 :值可以有可以没有,根据表达式获取变量类型。推导表达式内部的类型

复制代码
int main()
{
	int x = 10;
	// 规则1: 变量名 -> 变量类型
	decltype(x)a;//a的类型位x的类型
	cout << sizeof(&x) << endl;
	// 规则2: 表达式 -> 表达式结果的类型
	decltype(x + 5) c;    // int
}

lamdba表达式

概念:用于定义并创建匿名的函数对象,极大的提高了代码的简洁性和表现力。

语法:capture (parameters) mutable -> return-type { function-body }

  • [] - (捕获外部变量)

  • () - (参数列表)

  • mutable - (可修改性)

  • -> type - (返回类型)

  • {} -函数体

    复制代码
    #include<iostream>
    using namespace std;
    int main()
    {
    	//最简单的形式
    	auto simple = []() {
    		cout << "hellow" << endl;
    		};
    	simple();
    	//当没有参数时,可以省去()
    	auto simple2 = []{
    		cout << "hellow2" << endl;
    		};
    	simple2();
    	//通过赋值的方式,获取之前定义的变量,用于表达式中
    	int x = 10, y = 20;
    	//生成一个副本,此副本在函数中可修改并且不会原x
    	auto simple3 = [x]()mutable ->int{//->int 可以省略
    		++x;
    		return x;
    		
    	};
    	auto temp = simple3();//11
    	auto temp2 = simple3();//12
    	cout << temp << " " << temp2<<endl;
    
    	//引用捕获,不会生成副本,而是会影响原y
    	auto simple4 = [&y]()mutable {
    		y++;
    		};
    	simple4();
    	cout << y << endl;
    
    	//获取所以外部变量,生成副本
    	auto simple5 = [=]() {
    		cout << x << " " << y << " " << temp << " " << temp2 << endl;
    		};
    	simple5();
    	//引用捕获所有外部变量[&] ,  [a, &b]     // 值捕获a,引用捕获b   [=, &c] // 值捕获所有,但c是引用捕获     [&, a]// 引用捕获所有,但a是值捕获
    
    }

委托构造

概念:使用当前类的其它构造函数来协助当前构造函数初始化操作,允许一个构造函数调用同一个类的另一个构造函数。

作用:在有一些对象需要使用特有且相同的构造函数时,就可以使用这个委托构造,直接设置好内部的一些成员变量的值,然后构造。比如:先写一个完整的构造函数,其中包含性别的选项,就可以在委托函数中直接定义好男性这个成员变量,此时生成对象时就可以直接调用这个委托构造。

普通构造和委托构造区别

它俩都是一个成员初始化值列表与一函数体

委托构造函数的成员初始化值列表只有唯一的参数就是构造函数

当被委托构造函数当中函数体有代码,那么会先指向函数体

委托构造函数的创建

在其中直接调用普通构造函数

复制代码
#include<iostream>
using namespace std;

class member {
public:
	member(int a, int s, string n)
	{
		age = a;
		sex = s;
		name = n;
		cout << "调用构造函数" << endl;
	}
	member(string n) :member(18, 1, n)
	{
		cout << "调用委托构造" << endl;
	}

private:
	int age;
	int sex;
	string name;
};
int main()
{
	//单独创建18岁男性成员
	member m1("jack");
	member m2("mike");
    //创建其它成员
	member m3(20, 0, "li");
	
}

继承构造函数

概念:继承构造函数允许派生类直接继承基类的构造函数,就像"子承父业",避免在派生类中重复编写相同的构造函数。

复制代码
#include<iostream>
using namespace std;

class Base {
public:
	Base(int a) {
		x = a;
		cout << "x=" <<x<< endl;
	}
	Base(int a, int b)
	{
		x = a;
		y = b;
		cout << "x=" << x<<" " << "y=" << y << endl;
	}

private:
	int x;
	int y;
};

class Derived1 :public Base {
public:
	//普通继承,手写构造函数
	Derived1(int x) :Base(x) {
		cout << "普通继承Derived1" << endl;
	}
	Derived1(int x, int y) :Base(x, y)
	{
		cout << "普通继承Derived1" << endl;

	}
};
class Derived2 :public Base {
public:
	//继承构造
	using Base::Base;
};
int main()
{
	Derived1 d1(10);
	Derived1 d2(12,20);
	cout << "继承构造" << endl;
	Derived2 d3(20);
	Derived2 d4(40,60);

}

作用:解决了传统方式的代码冗余问题。

注意事项:

array

是C++11引入的固定大小数组容器,它就像是给C风格数组穿上了"STL的外衣",既保持了原始数组的性能,又提供了现代容器的安全性。

复制代码
#include<iostream>
#include<array>
#include <algorithm>
#include <numeric>
using namespace std;
int main()
{
	//默认初始化
	array<int, 4> a;//{0,0,0,0}
	a = { 10,10,22,3 };

	//列表初始化
	array<int, 4> a2 = { 10,2,3,40 };
	array<int, 4> a3{ 10,20,30,40 };

	//部分初始化,剩余为0
	array<int, 5> a4 = { 1,2 };//{1,2,0,0,0}

	//拷贝构造的方法
	array<int, 5>a5(a4);

	//元素访问

	cout << a5[2] << endl;//不会抛出异常

	try {
		cout << a5.at(5) << endl;//越界会自动抛出异常std::out_of_range异常
	}
	catch (std::out_of_range)
	{
		cout << "越界" << endl;
	}
	// 3. front()和back()访问首尾元素
	std::cout << "第一个元素: " << a5.front() << std::endl;  // 1
	std::cout << "最后一个元素: " << a5.back() << std::endl;  // 0

	int* ptr = a5.data();//获取底层指针

	cout << *(ptr + 1) << endl;//2
	for (auto it = a5.begin(); it != a5.end(); it++)//迭代器
	{
		cout << *it << " ";
	}
	cout << endl;
	//使用stl的函数算法
	sort(a5.begin(), a5.end());//{0,0,0,1,2}
	for (auto it = a5.begin(); it != a5.end(); it++)//迭代器
	{
		cout << *it << " ";
	}
}

forward_list

概念:c++11新增加的一类容器,底层实现与list容器类似,采用链表结构,只是是一个单向链表,而list位双向链表,只保留向前遍历的能力,以换取更好的性能。

区别

特性 std::list std::forward_list std::vector
遍历方向 双向 单向 随机访问
每个节点开销 2个指针 1个指针 无额外开销
内存局部性 优秀
插入删除 O(1)任意位置 O(1)在已知位置后 O(n)中间插入
size()方法 无(C++11)
复制代码
#include<iostream>
#include<forward_list>
using namespace std;
int main()
{
	//单向链表不支持随机访问和访问back的方法
	//只能访问头节点
	std::forward_list<int> flist = { 10, 20, 30, 40 };

	std::cout << "第一个元素: " << flist.front() << std::endl;  // 10
	//可以使用迭代器遍历

	for (auto it = flist.begin(); it != flist.end(); ++it) {
		std::cout << *it << " ";
	}
	std::cout << std::endl;

	//插入方法,使用迭代器的方式

	flist.push_front(5);//头部插入
	flist.emplace_front(1);//头部插入
	for (auto it = flist.begin(); it != flist.end(); ++it) {
		if (*it == 10)
		{
			flist.insert_after(it, 15);//在10的节点后面插入新节点
		}
	}
	for (auto it = flist.begin(); it != flist.end(); ++it) {
		std::cout << *it << " ";
	}
	std::cout << std::endl;

注意事项:插入和删除操作只能针对头部位置和当前节点的后面节点,不能操作前节点。并且删除当前节点的时候,迭代器会失效,要进行接收删除操作的返回值。

垃圾回收机制

概念:自动内存管理机制,就像有个"内存管家"自动帮你清理不再使用的内存,防止内存泄漏。

c++没有垃圾回收的原因:系统处理时的开销、设计耗内存、替代方法(析构函数)、没有共同基类

c/c++经典垃圾回收算法:引用技术算法(通过计数的方式查看内存是否使用,类似于unique_ptr)、标记清除算法(从根节点遍历所有节点,如果无法遍历的地方,就需要清除),节点拷贝算法(把整个堆分成两个半区,将一个半区的拷贝到另一半区,就可以解决内存碎片问题)

正则表达式

概念:正则表达式是文本模式匹配的工具,就像"文本搜索的超级放大镜",可以快速找到符合复杂规则的字符串

复制代码
#include <regex>  // 主要头文件

// 🎯 核心类:
std::regex     // 正则表达式对象
std::smatch    // 匹配结果(字符串)
std::cmatch    // 匹配结果(C字符串)
std::sub_match // 子匹配结果
std::regex_iterator  // 正则迭代器
std::regex_token_iterator // 正则令牌迭代器
#include <regex>
#include <iostream>

void regexMatchDemo() {
    // 🎯 regex_match - 整个字符串必须完全匹配模式
    std::regex date_pattern(R"(\d{4}-\d{2}-\d{2})");
    
    std::string valid_date = "2024-03-20";
    std::string invalid_date = "2024-03-20 extra";
    
    if (std::regex_match(valid_date, date_pattern)) {
        std::cout << "有效日期格式" << std::endl;
    }
    
    if (!std::regex_match(invalid_date, date_pattern)) {
        std::cout << "无效日期格式" << std::endl;
    }
    
    // 🎯 提取匹配组
    std::regex detailed_date(R"((\d{4})-(\d{2})-(\d{2}))");
    std::smatch matches;
    
    if (std::regex_match(valid_date, matches, detailed_date)) {
        std::cout << "完整匹配: " << matches[0] << std::endl;  // 2024-03-20
        std::cout << "年份: " << matches[1] << std::endl;      // 2024
        std::cout << "月份: " << matches[2] << std::endl;      // 03
        std::cout << "日期: " << matches[3] << std::endl;      // 20
    }
}

智能指针

概念:智能指针是自动管理内存生命周期的RAII包装器,就像"内存管家",自动处理资源的分配和释放。

头文件:<memory>

智能指针家族对比

智能指针 所有权语义 拷贝语义 使用场景
unique_ptr 独占所有权 禁止拷贝,允许移动 单一所有者资源
shared_ptr 共享所有权 引用计数,可拷贝 共享资源
weak_ptr 观察所有权 不增加引用计数 打破循环引用
auto_ptr 独占所有权 已废弃 C++98遗留

unique_ptr:独占指针(内存占为己有,不支持拷贝和赋值)。unique_ptr对象在它们本身被销毁后立即删除它们管理的对象(使用删除器 ),或者一旦它们的值通过赋值作或显式调用 unique_ptr::reset 更改,它们就会自动删除它们管理的对象(使用删除器)。

特性:

  1. 一般用make_unique创建对象

  2. 可以通过move(指针)转移所有权,它自身会被释放

  3. 显式释放资源用reset或者出作用域自动释放

  4. relase可以释放空间,把空间返回,这块空间可以被指针接收

  5. get可以获得智能指针的的空间

  6. unique_ptr的"独占"指的是所有权独占,不是访问权独占。这种设计既保证了内存安全,又提供了必要的灵活性!

    #include
    #include
    using namespace std;

    int main()
    {
    unique_ptr u1 = make_unique(10);
    int* a = u1.get();//获取u1指向的地址
    *a = 100;

    复制代码
     int* b = u1.release();//释放u1的空间,并把空间给b
     *b = 50;
     cout << *a << endl;
     cout << u1 << endl;
     
     unique_ptr<int>u2(new int (100));
     
    
     //unique_ptr<int>u3(u2);//无法进行拷贝构造
    
     unique_ptr<int> u3(move(u2));//移动语义可以将u2的指向的内存给u3
     cout << u2 << endl;
     cout << *u3 << endl;
    
    
     //u2.reset();//手动销毁空间
     //cout << u2 << endl;
    
     //作用域结束自动释放内存

    }

shared_ptr:引用计数指针(共享拥有同一堆分配对象的内存,支持复制和赋值操作),通过计数的方式表明当前指针指向的内存有几个指针指向。一旦计数变为0,对象就会销毁。这在非环形数据结构中防止资源泄露很有帮助。

特性:

  1. make_shared函数:最安全的分配和使用动态内存的方法,返回shared_ptr。 将对象和控制块分配在连续内存。构造函数(new 空间)

  2. 可以与其它指针共享同一块内存,通过use_count得到同一块空间的智能指针个数

  3. 大部分成员函数与unique_ptr相同

    int main()//引用计数指针
    {
    shared_ptr s1(new string ("jack"));
    shared_ptr s2 = s1;//两个指针指向同一块空间
    cout << s2.use_count() << endl;//2
    s2.reset();
    cout << s1.use_count() << endl;//1
    }

weak_ptr智能指针:配合shared_ptr而引入的一种智能指针协助shared_ptr工作。它的构造和析构不加或减少计数。并不用于资源的所有权,所以不能直接使用资源,但是可以查看shared_ptr的一些状态

特性:

  1. 与share_ptr相结合,但不参与计数,但可以指向同一块区域。
  2. 能够解决循环引用的问题(技术指针互相连接)
  3. 不能对指向的空间*引用,因此不能操作指向的空间的对象
  4. 目的是解决shared_ptr的循环引用问题
  5. 不能直接访问对象,必须通过lock()方法获取临时所有权
  6. 当所有shared_ptr都销毁对象后,weak_ptr能感知到对象已不存在,通过expired()

常用场景:

当两个或多个对象通过shared_ptr相互引用时,会形成循环引用:

  • 对象A持有对象B的shared_ptr

  • 对象B持有对象A的shared_ptr

  • 引用计数永远不会归零

  • 内存泄漏发生

weak_ptr通过打破所有权循环来解决这个问题:

  • 单向所有权 :将其中一个方向的引用改为weak_ptr

  • 不增加引用计数weak_ptr不参与引用计数计算

  • 需要时临时获取所有权 :通过lock()在需要访问时临时获得所有权

    class Node : public enable_shared_from_this {
    public:
    shared_ptr next;
    shared_ptr prev; // 🚨 循环引用!

    复制代码
      // ✅ 解决方案:使用weak_ptr
      weak_ptr<Node> weak_prev;
    
      ~Node() {
          cout << "Node 析构" << endl;
      }

    };

    void demonstrateCyclicReference() {
    auto node1 = make_shared();
    auto node2 = make_shared();

    复制代码
      node1->next = node2;
      node2->prev = node1;  // 循环引用,内存泄漏!
    
      // 使用weak_ptr避免循环引用
      node2->weak_prev = node1;

    }

关键字nullptr/constexpr

nullptr:替换NULL

consterpr:constexpr是C++11引入的关键字,用于声明编译期常量表达式。它的核心目标是让计算在编译阶段完成,而不是运行时。

复制代码
#include<iostream>
using namespace std;
int max(int a, int b)
{
	return a < b ? b : a;
}
int main()
{
	constexpr int compile_const = 42;       // 编译期初始化
	const int a = max(10, 11);//运行时初始化

}

Function

概念:std::function是一个通用的函数包装器,可以存储、复制和调用任何可调用对象。就像"函数容器",能容纳各种类型的函数。

函数对象类型:

  1. 普通函数
  2. lambda函数
  3. 结构体或类函数(通过operator实现的)

小点: 结构体或类函数对象优点:

成员函数是与类关联的函数 ,它们操作类的对象并访问其内部状态。与普通函数不同,成员函数隐式接收一个指向调用对象的指针(this指针)。

  1. 性能优越 - 可被编译器内联优化

  2. 状态保持 - 可以拥有成员变量和状态

  3. 类型安全 - 编译时类型检查

  4. 高度灵活 - 可模板化、可继承、可组合

  5. 现代C++集成 - 与Lambda、STL完美配合

    #include
    #include
    using namespace std;
    void print()
    {
    cout << "hellow " << endl;
    }
    struct print2
    {
    void operator ()()
    {
    cout << "func3" << endl;
    }
    };
    class print3 {
    public:
    void operator()()
    {
    cout << "func" << ":" << id << endl;
    }

    private:
    int id = 4;
    };
    int main()
    {
    function<void()> func1 = print;
    function<void()> func2 = {
    cout << "func2";
    };
    function<void()> func3 = print2();
    function<void()> func4 = print3();

    复制代码
     func1();
     func2();
     func3();
     func4();

    }

共享内存

概念:共享内存是一种进程间通信(IPC)机制 ,允许多个不相关的进程访问同一块物理内存区域。这是速度最快 的IPC方式,因为数据不需要在内核和用户空间之间复制。共享文件句柄方式共享内存,正式名称为内存映射文件(Memory-mapped Files),它通过将磁盘文件映射到进程的虚拟地址空间,实现多个进程对同一内存区域的共享访问。

服务端创建共享内存区域部分,内存映射到当前应用程序进程,写入数据信息

客户端打开共享内存区域部分、内存映射到当前应用程序进程、读出数据信息

atomic_flag

概念:原子布尔类型,不同于atomic的特化,保证免锁,不提供加载或存储操作.它只支持两个操作:设置和清除。就像"原子开关",只能开或关。

特性 说明 优势
无锁保证 在所有平台上都是无锁的 性能最高
最小开销 只有两个状态操作 内存占用最小
简单接口 只有test_and_set()和clear() 使用简单

语法:

①通过test_and_set设置标志并返回旧值

概念:实现多线程间的同步操作,当条件不满足,相关线程会一直阻塞,当某种条件满足时,线程才会唤醒。

std::thread多线程

join和detch的区别

异常处理exception类

c++中的一些类代表异常,都是从exception类派生出来的,因此要包含stdexcept头文件,来调用这些异常

复制代码
// ❌ 错误理解:认为会抛出异常
try {
    a[10] = 100;  // 期望:抛出 std::out_of_range
} catch (const std::out_of_range& oor) {
    // 实际上永远不会执行到这里!
}

// ✅ 实际情况:未定义行为
a[10] = 100;  // 可能:
              // - 静默覆盖相邻内存
              // - 程序崩溃
              // - 产生随机结果
              // - 任何其他奇怪行为

#include <iostream>
#include <stdexcept>
#include <vector>

int main() {
    std::vector<int> a(10);  // 使用vector而不是原生数组
    
    try {
        a.at(10) = 100;      // ✅ 使用at()方法,会进行边界检查
    }
    catch (const std::out_of_range& oor) {
        std::cerr << "Out of Range error: " << oor.what() << '\n';
    }
    
    return 0;
}

关键知识点:

  • 原生数组不进行边界检查,越界访问是未定义行为

  • 只有std::vector::at()std::array::at()会抛出std::out_of_range

  • 现代C++推荐使用标准容器而非原生数组

  • 始终优先选择安全的访问方法

相关推荐
程序员二叉几秒前
【JVM】OOM详解+JVM参数+FullGC排查+CPU飙高+死锁+内存泄漏+命令大全
java·开发语言·jvm·面试
yijianace12 分钟前
Python线程与多线程完全总结(从入门到理解并发本质)
开发语言·python
不知名的老吴20 分钟前
线程的生命周期之线程同步
java·开发语言·jvm
为何创造硅基生物20 分钟前
独占指针的创建std::make_unique 本身自带堆出现
c++
kyle~32 分钟前
ROS 2 与 Isaac Sim 联合仿真(一)体系架构、环境选型与基础通信闭环
c++·机器人·nvidia·仿真·ros2
努力努力再努力wz1 小时前
【内存管理与高并发内存池系列】从 mmap 到 malloc:文件映射、匿名映射与 glibc 内存分配机制详解
linux·c语言·数据结构·数据库·c++·qt·链表
J2虾虾1 小时前
C 语言 void 完全用法
c语言·开发语言
八解毒剂1 小时前
数据结构-平衡二叉树——对二叉搜索树的优化
数据结构·c++·算法
会Tk矩阵群控的小木1 小时前
基于Python的iMessage短信群发与社媒多账号统一管理系统实现
开发语言·windows·python·新媒体运营·开源软件·个人开发
我是一颗柠檬1 小时前
【Java项目技术亮点】分库分表+数据路由策略:单表5000万后的架构升级方案
java·开发语言·分布式·架构