C++知识点总览

1.输入输出流

在C中要想输入和输出 我们会经常用到

#include <stdio.h>

在C++中头文件的命名风格不用.h

#include <iostream>

using namespace std;

为什么要用上面俩句话的解释(自己写的博客)

c++中 为什么要写<iostream>和using namespace std;-CSDN博客

cin cout

这俩个函数是在std命名空间下定义的

#include <iostream>
using namespace std;
int main() {
	int a;
    cin>>a;
	cout << "a的数值是:" << a << endl;
	return 0;
}
#include <iostream>
int main() {
	int a = 10;
   	 std::cin >>a;
	std::cout << "a的数值是:" << a << std::endl;
	return 0;
}

ceil()函数向上取整

round( )四舍五入取整

#include <iostream>

using namespace std;
int main() {
	int a = 10;
	cout<<ceil(3/2.0)<<endl;
	return 0;
}

2.bool类型

c语言没有 是C++中的一种基本数据类型(内置数据类型)

只用一个字节

0为false

1为true

非零的数组都是true

3.对象的含义

一块有类型的内存

4.用new关键字申请堆区空间(delete)

我们在C语言中在堆区申请内存空间是用malloc 释放用free

如果不释放会造成这段内存但用以后无法被访问 造成内存的泄露

在c++用我们new 开辟堆区空间 delete释放堆区空间

#include <iostream>

using namespace std;
int main() {
	//int* p = (int*)malloc(sizeof(int));C语言中
	int* p = new int;
	return 0;
}

new与malloc相同的是 都可以返回开辟这段空间的首元素的地址

都是不同点是 new返回的地址类型是自动匹配存储信息的内容的,而malloc是返回的空类型需要强制类型转换

#include <iostream>

using namespace std;
int main() {
	//int *p=(int*)malloc(sizeof(int)*4);
	int* p = new int[4] {1,2,3,4};//在堆区中开辟了4个int这么大小的空间 并返回了它的首元素的地址
	for (int i = 0; i < 4;i++) {
		cout << p[i] << " ";
	}
	return 0;
}

注意在开辟空间的时候初始化 在c中用malloc是不可以的,但是C++的new可以实现这个功能

中间是没有等号的

如果不初始化里面的数据是随机数

delete

#include <iostream>

using namespace std;
int main() {
	//int *p=(int*)malloc(sizeof(int)*4);
	int* p = new int[4] {1,2,3,4};//在堆区中开辟了4个int这么大小的空间 并返回了它的首元素的地址
	for (int i = 0; i < 4;i++) {
		cout << p[i] << " ";
	}
	delete[]p;//把p所指向的那段空间都释放了
	//delete p//只是把那段空间的第一个元素释放了,与free()不同
    //如果不是放p所指向堆区的那段空间 会造成内存的泄露
    p=NULL;
	return 0;
}
#include <iostream>

using namespace std;
int main() {
	//int *p=(int*)malloc(sizeof(int)*4);
	int* p = new int(3);//初始化1个数据
	return 0;
}

1.什么是引用

在C++语法上来看,引用是给变量起了个别名,不占用空间

2.引用的格式

类型& 引用变量名 =引用实体

3.引用的注意事项

a.引用定义的同时必须进行初始化,且不能初始化为空

引用是给变量起别名,那前提肯定得有实体变量

引用是给变量起别名,给空起了个别名没意义

c.不可以改变引用关系

这与引用的实质有关系

引用的实质是一个指针常量,是一个指向方向固定的指针,所有它不能更改指向,也就不能改变引用关系

int& b=a;
int* const b=&a;
image-20231127111656885
#include <iostream>

using namespace std;

int main() {
	int a = 5;
	int& b3;//引用必须初始化
	int& b2 = NULL;//引用不能初始化为空
	int& b1 = a;
	int* const b1 = &a;//本质 -->指针常量(指向不能改变,但是指向内存空间里面的内容可以改变)n
	cout << b1 << " " << *b2;
	return 0;
}

4.C++中的左值和右值

左值

可寻址、有变量名的值

右值

不可寻址、一般没有变量名的值

左值引用

int a=10;//a为左值 有变量名 可寻址(&x)可以找到地址
int& b=a;

右值引用

#include <iostream>
using namespace std;
int main() {
	int&& c = 10;//10为右值 不可寻址(&10错误) 
	return 0;
}

万能引用

const 类型 引用名 =引用实体

int main() {
	const int a = 10;
	const int& b = a;
	const int& c = 10;
	return 0;
}

5.引用的应用

做函数的形参

参数的传递方式有俩种

值传递(只是一个传值的过程),但是传递的值可能是地址,也可能是基本类型数据

引用传递

#include <iostream>

using namespace std;
void text1(int a,int b) {
	int t;
	t = a;
	a = b;
	b = t;
}//值传递
void text2(int*a,int*b) {
    assert(a);
    assert(b);
	int t;
	t = *a;
	*a = *b;
	*b = t;
}//址传递
void text3(int& a,int& b) {//int& a=a; int& b=b; 
	int t;
	t = a;
	a = b;
	b = t;
}//引用传递
void text4(int* const a,int* const b) {//int* const a=&a; int* const b=&b;
	int t;
	t = *a;
	*a = *b;
	*b = t;
}
int main() {//引用做函数的形参 可以修改传递过来实参变量的值
	int a = 5;
	int b = 10;
	text1(a, b);//值传递
	text2(&a,&b);//址传递
	text3(a,b);//引用传递
	text4(&a,&b);
	return 0;
}

优点

指针要判空的,但是引用不用 可以避免很多麻烦

引用做函数的返回值

在内存中不会产生返回值的副本

6.引用与指针的区别

1.引用必须进行初始化,但是指针可以不初始化,为野指针

2.引用不用初始化为空,但是指针可以初始化为空,为空指针

3.引用与变量的关系不能改变,但是指针的指向确可以改变

4.引用所占的空间大小为 类型大小所对应的字节,而指针4/8Byte

#include <iostream>
using namespace std;
int main() {
	int a = 10;
	int& b = a;
	int* c = &a;
	cout<<"引用的大小:" << sizeof(b)<<endl<<"指针变量的大小:"<<sizeof(c)<<endl;
	return 0;
}
image-20231127113815831

5.没有多级引用,但是有多级指针,二级三级指针

6.引用的++是指别名所在的那段空间数据的++,而指针的++是指向的移动

7.引用作为函数返回值的好处

可以避免调用拷贝构造函数,节约时间的成本

1.函数默认参数

函数栈

函数调用,是主调函数向被调函数传值,然后被调函数返回结果给主调函数的一个过程

这个过程是需要函数栈来辅助的

栈是向下生长的,就是由高地址向低地址开辟空间

堆是向上生长的,就是由低地址向高地址开辟空间

形参在入栈的时候是从右向左入栈的,而实参传值给形参的时候是从左向右传值(相当于出栈的顺序)

函数的默认参数是在函数调用的过程中发生的,实参在没有传递给形参值之前,形参就已经有了默认参数

格式:返回值类型 函数的名字(形参=默认值){ }

#include <iostream>

using namespace std;
int func(int a = 1, int b = 2, int c = 3) {//函数形参的默认参数
	cout << "你好" << endl;
}
int func1(int a = 1, int b, int c = 3) {
	cout << "OK" << endl;
}
int main() {
	func(3, 2, 1);
	//预期:30 20 10
	func1(2, 3);
	return 0;
}

剖析下上面程序的执行过程当函数执行到主函数func位置时,会由函数的调用,主调函数调用被调函数,被调函数会调用一个新的函数栈,被调函数的形参从右向左入栈,一次是c、b、a,然后主调函数向被调函数传值,按照从左到右的顺序传值

注意:如果某个位置有默认值,那么他的后面一般也要有默认值

原因:主函数中你认为值2会传给b但是实际上 他是按从左到右的顺序去传递的 2被赋值给了a b没有被赋值 所以会报错

#include <iostream>

using namespace std;
int func1(int a = 1, int b, int c = 3) {
	cout << "OK" << endl;
}
int main() {

	func1(2);
	return 0;
}

2.函数的重载

为什么C++有重载但是C语言却没有呐?

原因:C语言函数的组成与C++函数组成有区别

C中函数就是由函数名组成的

C++函数是由函数名+参数组成的

也就是说C++中参数不同但函数名相同的函数是不同的函数,而C语言里只要函数名一样就是相同函数了

所以C语言中不存在在函数的重载

函数重载的作用

函数名可以相同,提高复用性

在调用相同函数名的函数的时候,通过参数列表的不同来却分到底进入哪个函数

参数列表的不同可以是参数个位的不同,参数顺序的不同,参数类型的不同等等

#include <iostream>

using namespace std;
void fun(int a, int b) {
	cout << 1 << endl;
}
void fun(int a, float b) {
	cout << 2 << endl;
}
void fun(double a) {
	cout << 3 << endl;
}
void fun(float a) {
	cout << 4 << endl;
}
int fun(int a, double b) {
	return 5;
}
//int fun(float a) {
//	cout << 5 << endl;
//}//报错了 因为函数的重载就函数名相同参数不同 返回值对函数重载的判断不起作用
void fun(int a, int b, int c) {
	cout << 5 << endl;
}
int main() {
	fun(1, 1);
	fun(1, 1.1f);
	cout << fun(1, 1.1)<<endl;
	fun(1.1);
	fun(1.1f);
	return 0;
}

当引用作为函数重载的条件

#include <iostream>

using namespace std;
void func(int &b) {
	cout << "1" << endl;
}
void func(const int &b) {
	cout << "2" << endl;
}
void func(int&& b) {
	cout << "3" << endl;
}
int main() {
	int a = 2;
	func(a);
	func(2);
	return 0;
}

引用分为左值引用 右值引用 和 万能引用

可以用这个来作为函数参数的不同

函数重载遇到函数默认参数

观看下面的示例,确实符合函数重载的定义(函数名相同,参数列表不同可以重载),但是仔细观察第一个函数是有一个默认参数的,理论上它传递2个值就可以。而第二个函数就是传递俩个参数,这就会造成编译器无法确定你到底要将这俩个值传递给谁

#include <iostream>

using namespace std;
void func(int a,int b,int c=1) {
	cout << "1" << endl;
}
void func(int a,int b) {
	cout << "2" << endl;
}
int main() {
	func(1,2);
	return 0;
}

一个函数是否能够重载与它的返回值无关

#include <iostream>

using namespace std;
void func(int a, int b) {
	cout << "1"<<endl;
}
int func(int a, int b) {
	cout << "2" << endl;
}
int main() {
	func(1, 1);
	return 0;
}

编译器无法仅根据返回值的类型来判断是否能够重载

函数的重载是一个静态的多态,它先编译阶段就能够判断运行的是哪个函数

3.递归

递归的模板(4件套)

1.结束条件

2.前进

3.递归的调用

4.回退

int func(){
    if();//递归的结束条件
    //前进
    func();//递归的调用
    //回退
}

斐波那契数列的递归

为什么斐波那契数列能用递归

能写出递归树

比如你想算第五项的斐波那契数列

你要想求5 就必须得知道4和3 你要想知道4和3 就必须得知道2和1 你要想知道 2和 1 就必须知道1 和 0 而1 和0 我们还真知道 所以符合递归

递归树如图:

image-20231130121659310
int Fb(int n) {
	if (n == 0) return 0;
	if (n == 1) return 1;
	int ret=Fb(n-1)+Fb(n-2);
	return ret;
}
int main() {
	Fb(5);
	return 0;
}

爬楼梯的递归

int Tj(int n) {
	if (n == 1) return 1;
	if (n == 2) return 2;
	int ret=Tj(n-1)+Tj(n-2);
	return ret;
}
int main() {
	Tj(5);
	return 0;
}

递归树如图:

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

1.由struct类过度到类

C中的struct类与C++中struct的区别

我们在C语言中都学过struct类型 他是一种复合类型 里面可以定义各种基本类型的变量 但是不知道你是否留意 struct类里面不能定义函数

例子1:

#include <stdio.h>
struct Student {
	int a;
	int b;
	void show() {
		//非法的
	}
};
int main() {
	
	return 0;
}
#include <iostream>

using namespace std;

struct Student {
	int a;
	int b;
	void show() {
		//合法的
	}
};
int main() {
	
	return 0;
}

通过上面的这个例子我们可以看出,

struct类型在C中不可以定义函数,但是在C++中可以定义函数

在看一下第二个例子

例子2

#include <stdio.h>

struct Student {
	int a;
	int b;
};
int main() {
	struct Student s1;//合法的
	Student s1;//非法的
	return 0;
}
#include <iostream>

using namespace std;

struct Student {
	int a;
	int b;
	void show() {
		//合法的
	}
};
int main() {
	struct Student s1;//合法的
	Student s1;//合法的
	return 0;
}

通过例子2我们可以看出,在C中如果不用typedef关键字,我们定义结构体变量只能用struct Student,使用Student是不合法的,而在C++中这俩种方式都是合法的

在C中结构体名称不可以定义变量,但是C++中结构体名称可以定义变量

结合这俩点我们不难看出 C++对结构体类型进行了某种程度的升级

“某种程度”–>函数的升级,以及定义“变量”的升级,都更加方便了

这种升级就是把struct升级成了C++语言中的类

这就是我们今天的主题,在这里就引出了

2.类

可以把类看成就C中struct类型的plus版

结构体和类的唯一区别就是默认访问权限和继承权限

C++面向对象的三大特点

1.封装

2.继承

3.多态

!!C++中认为万事万物皆为对象,对象有它的属性和行为

属性–>成员变量

行为–>成员函数

封装

格式:class 类名 { 访问权限:属性、行为};

不要忘了分号!!!

int a=10;

和int一个a等于10一样,必须加分号

意义:讲属性和行为放在一起,并加以权限控制

C语言太自由了主函数中向修改结构体类型里面变量的值就修改

C++把它们封装了起来,加了访问权限的控制

访问权限:公有的、受保护的和私有的

1.public

2.protected

3.private

C++中类下默认访问权限是private

struct类的默认访问权限是public,因为要兼容C中的struct类

#include<iostream>
using namespace std;
struct Student {
	//struct类里面没有说明什么权限 默认权限是public
	//class类里面没有说明什么权限 默认权值是private
	string _name;
	string _num;
	void Set_name(string name) {
		_name = name;
	}
	void Set_num(string num) {
		_num = num;
	}
	void show() {
		cout << _name << endl << _num << endl;
	}
};
int main() {
	Student s1;//合法的
	s1._name = "123";//合法的默认是public
	return 0;
}
#include<iostream>
using namespace std;
class Student {
	//struct类里面没有说明什么权限 默认权限是private
	//class类里面没有说明什么权限 默认权值是class
	string _name;
	string _num;
	void Set_name(string name) {
		_name = name;
	}
	void Set_num(string num) {
		_num = num;
	}
	void show() {
		cout << _name << endl << _num << endl;
	}
};
int main() {
	Student s1;//合法的
	s1._name = "123";//非法的 默认位private 访问不到
	return 0;
}

类的作用域

public–>共有的作用域为 类内函数、子类和对象
protected–>受保护的作用域为 类内函数、子类
private–>私有的作用域 类内函数

this关键字

先看个例子

#include<iostream>
using namespace std;
class Student {
	//struct类里面没有说明什么权限 默认权限是private
	//class类里面没有说明什么权限 默认权值是class
private:
	string _name;
	string _num;
public:
	void Set_name(string name) {
		_name = name;
	}
	void Set_num(string num) {
		_num = num;
	}
	void show() {
		cout << _name << endl << _num << endl;
	}
};
int main() {
	Student s1;//合法的
	Student s2;
	s1.Set_name("syc");//语句1
	s2.Set_name("lh");//语句2
	return 0;
}

语句1要执行的是把字符串syc赋值给s1的_name

语句2要执行的是把字符串lh赋值给s2的_name

this指针里面存的就是s1和s2的地址

this关键字是指向当前对象的指针,谁调用这个函数,this指针就指向谁
本质:this指针的本质是在实参传值给形参的过程中,形参多了一个指针常量this用来接收调用这个函数对象的地址
#include<iostream>
using namespace std;
class Student {
private:
	string _name;
	string _num;
public:
	void Set_name(Student* const this1,string name) {//指针常量 指向方向不变
		this1->_name = name;
	}
	void Set_num(Student* const this2,string num) {
		this2->_num = num;
	}
	void show() {
		cout << _name << endl << _num << endl;
	}
};
int main() {
	Student s1;//合法的
	Student s2;
	s1.Set_name(&s1,"syc");
	s2.Set_name(&s2,"lh");
	return 0;
}

特点:其实this指针的特性在上一个模块(this指针的引出)已经讲解的差不多了,这里简要说明:

1.this指针的类型:指针常量指向调用这个函数的对象
2.只能在“成员函数”的内部使用
3.this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所4.以对象中不存储this指针。
5.this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递

此处为摘抄。

类的对齐和结构体的对齐很相似

注意:计算类的大小不包括成员函数,只计算成员变量

1.构造函数

什么是构造函数?

构造函数是在创建对象的时候对成员变量进行赋值的一个函数

<注意>

构造函数是对成员变量进行赋值,而不是初始化

因为是先有了对象同时对象里面的成员变量被实例化了,之后对象调用构造函数,对成员变量进行赋值

构造函数的格式

类名+(){ }

观察这个格式我们可以看出它相对于普通函数的相同点和不同点

相同点:它与普通函数一样都有函数名、参数列表、函数体

不同点:它没有返回值 并且它强制要求函数名必须是类名

把类名作为函数名可以很明显的区分出来 这个是构造函数 方便你找到它

无参默认构造函数

解释:如果在类里面没有自己定义构造函数的话,编译器会自动提供一个无参的默认构造函数

<注意>:如果定义了构造函数,那么编译器不会提供默认构造函数

构造函数的分类及调用

分类

按参数分:有参构造、无参构造

按类型分:普通构造、拷贝构造

//构造函数的分类
Pointer() {//无参构造
	cout << "调用无参构造" << endl;
}
//explicit 
Pointer(int* _p1) {//禁止隐式调用的有参构造 
	cout << "调用有参构造 再堆区申请空间" << endl;
}
Pointer(int a) {//有参构造
	p1 = new int[a];
	cout << "调用有参构造" << endl;
}
构造函数的调用

1.括号调用(常用)

2.显示调用

3.隐式调用

如何禁止隐式调用–>使用explicit关键字

int main() {
	//构造函数调用时候的分类
	int a=2;
	int* pt = &a;
	Pointer s1(pt);			 //括号调用
	Pointer s2 = Pointer(pt);//显示调用
	Pointer s3=pt;			 //隐式调用-->只能作用于需要传递1个参数的构造函数
	//explicit 关键字 禁止隐式调用 
	Pointer* s4 = new Pointer(a);
	delete s4;
	return 0;
}
Pointer(int a) {//有参构造
	p1 = new int[a];
	cout << "调用有参构造" << endl;
}
Pointer* s4 = new Pointer(a);

这代码的内存分区图如下

image-20231204124533552

构造函数的具体实现代码

#include <iostream>
#include <cmath>
using namespace std;
class Pointer {
public:
	int* p1;
	int p2;
	//模拟的默认构造函数 当类内没有构造函数的时候 编译器会自己生成一个默认无参构造函数
	Pointer() {
		cout << "无参构造函数" << endl;
	}
	//有参构造函数
	Pointer(int n) {
		if (n) {
			p1 = new int[n];
			cout << "使用有参构造函数再堆区中开辟了" << n << "个int这么大小的空间 对象的成员变量p1指向这段空间" << endl;
		}
		else
			cout << "输入的n不合法" << endl;
	}
	//构造函数的重载 第二个函数与第三个函数 函数名相同但是参数列表不同符合函数重载
	Pointer(int a, int b) {
		p2 = a + b;
		cout << "构造函数有重载!" << endl;
	}
	//判断函数是否能够重载的时候要关注形参是否有默认参数
	//Pointer(int a, int b, int c = 1) {
	//	cout << "";
	//}//符合函数重载的条件,但是由于默认参数的出现,编译器懵逼了 都2个参数 不知道调用哪个了
};
int main() {
	Pointer s1;//调用默认无参构造
	Pointer s2();//看着也像是一个无参默认构造函数 但是编译器会把它识别成一个函数声明
	Pointer s3(5);//调用有参构造
	Pointer s4(1, 4);//构造函数也有重载 出现了默认参数
	//Pointer s5(1, 3, 5);//构造函数也有重载
	return 0;
}

注意

构造函数的重载

构造函数也像普通函数一样具有重载,及函数名都是相同的类名,参数列表不同,符合函数重载

但是要注意的和普通函数一样,当出现默认参数的时候,要仔细观察它是否符合函数重载

看下面这段代码:

Pointer() {
		cout << "无参构造函数" << endl;
	}
Pointer s2();//错误 看着也像是一个无参默认构造函数 但是编译器会把它识别成一个函数声明
Pointer s2;//正确

看着是不是很想是一个无参构造函数,但是实际上编译器很认为他是一个函数的声明;

在调用默认构造函数的时候不用加括号!!!!否则会被编译器认为是函数的声明!!!!!!!

2.析构函数

什么是析构函数?

用于释放成员变量指向的堆区空间

什么时候调用析构函数?

当对象要被销毁的前 编译器自动调用析构函数

析构函数的格式

~类名(){ }

与构造函数比 多了一个~表示析构函数

析构函数不存在重载,它参数列表参数为空

~Pointer() {
	cout << "调用了析构函数" << endl;
}
Pointer* s5= new Pointer();
delete s5;

delete先调用析构函数,释放掉成员变量所指向的堆区空间,然后调用free函数把对象所在的堆区空间释放

3.new与malloc的区别

从返回值上来看

new返回的地址是自动转换的

malloc返回的地址是需要强转的

从名称上

new是运算符,可以调用重载运算符函数(operator)进行重载

而malloc函数是C语言的库函数,C语言没有函数的重载,所以malloc没有函数重载

参数上来看

new是不需要传参的,它分配空间的大小由编译器根据类型计算得出

而malloc是需要传参的,传递的是具体开辟空间的大小的字节数

从底层在来看

new是先调用malloc函数,先在堆区中开辟,如果这段空间的类型是类的话,会调用构造函数,对对象里面的成员变量进行赋值

而malloc仅仅只是在堆区中开辟空间

从空间开辟失败的后果上来看

new开辟空间失败会抛出一段异常

而malloc会返回一个空指针

从已分配内存不够用扩张上来看

new不支持内存的扩张

malloc可以调用realloc扩张内存

4.delete与free的区别

1.从参数上来看–>delete不需要传递参数,free需要传参

2.从底层上来看

delete关键字会先调用析构函数,再调用free函数

具体过程如下:delete函数会先调用析构函数释对象中成员变量所指向的堆区空间,然后再调用free函数释放对象所在的堆区空间

而free函数,不会调用析构函数,只释放对象所在的堆区空间

问题1:为什么delete的底层不是先调用free函数再调用析构函数那??????

Pointer() {
		cout << "无参构造函数" << endl;
	}
Pointer s2();//错误 看着也像是一个无参默认构造函数 但是编译器会把它识别成一个函数声明
Pointer s2;//正确

看这段代码,它的内存分区图如下

image-20231204124533552

如果我们先调用的是free函数,那么对象所在的堆区空间会先被释放,那么成员变量如果有指向其他堆区空间的话,这段空间将会再也无法被找到了,会造成内存的泄露

但是如果先调用析构函数,先把成员变量指向的堆区空间释放了,就不会造成这个问题

!!!!故delete函数是先调用析构,再调用free

问题2:new可以和free一起搭配吗?

new正常搭配的是delete–>free+析构

所以当成员变量没在堆区申请空间的时候,也就是析构函数不起作用的时候 delete可以替换free 但是别给自己找麻烦还是别这么用了!

1.参数传递的俩种方式

a.值传递

实参把它的值赋值给了形参,这个赋值的过程就是值传递

址传递也是值传递,他是实参把地址赋值给了形参,这个过程是一个赋值的过程

对象在值传递的过程中会调用拷贝构造!!!!!!!!!!!

b.引用传递

2.为什么要有拷贝构造函数?

因为我们都知道一个基本数据类型给另一个相同的基本数据类型的赋值很简单,只需要通过赋值运算符一步就可以实现。

但是类的对象结构很复杂,里面有很多的成员变量,无法通过简单的赋值运算符就可以实现,所以把一个已经存在的对象给另外新的一个对象初始化的这个操作封装到了一个函数里面,这个函数就是拷贝构造函数。

3.拷贝构造的格式

类型(const 类 & other){ }

Person(const Person& other) {
		cout << "调用拷贝构造函数" << endl;
	}

通过格式我们可以看出拷贝构造函数与构造函数的不同点之一是他的参数是一个万能引用类型

请回答下面俩个问题

为什么拷贝构造的形参要用const?

拷贝构造的使用时机的第三条是当返回值是值形式的局部对象,那么它会调用拷贝构造函数拷贝一个匿名对象,既然它都是匿名的了,就是能通过它的名字取地址了,那么它肯定是右值。这样的话实参可能是右值也可能是左值,所以必须用万能引用

函数的返回值为右值,不可寻址。

class Person {
	Person(Person& other) {//缺少const的
		cout << "调用拷贝构造函数" << endl;
	}
};
int main() {
	Person s(text6());//括号调用,用匿名对象去初始化一个新的对象s
	Person s1 = text6();//隐式调用
	Person s2 = Person(text6());//显示调用,函数的返回值是右值,但是拷贝构造函数是万能引用,无所谓
	return 0;
}
image-20231208201148760

由于拷贝构造函数没有用const关键字,导致只能接收左值,但是函数的返回值是右值,无法接收了。

为什么拷贝构造的形参是引用类型?

拷贝构造是用一个已经存在的对象去初始化新对象,如果实参传递给形参,形参不是引用类型的话,会导致形参充当了一个新的对象,被实参初始化,满足定义,继续拷贝构造,一顿递归,死循环了!!!!!!!!!!

综合上面俩点,拷贝构造的形参必须是万能引用。

4.什么时候使用拷贝构造函数?

a.用一个已经存在的对象去初始化另一个新的对象

b.实参(对象)以值传递的方式传递给形参

c.以值的方式返回一个局部对象 (它会拷贝一个匿名的对象)

A func(){
    A a;
    return a;
}
void text(){
    A s=func();
}

它会先拷贝一个匿名名的对象假设是a‘ a’再调用拷贝构造函数给s赋值。但实践上编译器做了优化,可以直接用a调用拷贝构造函数去给s初始化

但是如果返回值为引用类型的话,他就不会调用拷贝构造,拷贝匿名对象了!!!!!

#include <iostream>
 
using namespace std;
class Person {
public:
	int a;
public:
	Person() {
		cout << "无参构造函数" << endl;
	}
	Person(int _a) {
		this->a = _a;
		cout << "调用有参构造函数" << endl;
	}
	Person(const Person& other) {
		cout << "调用拷贝构造函数" << endl;
	}
	~Person() {
		cout << "调用析构函数" << endl;
	}
};
//用一个已经存在的对象去初始化一个新的对象
void text1() {
	Person s1;
	Person s2(s1);//括号调用
	Person s3 = Person(s1);//显示调用
	Person s4 = s1;//隐式调用
}
//实参以值传递的方式给形参传值
void text2(Person s1) {
	
}
void text3() {
	Person s1;
	text2(s1);
}
//以值的方式返回局部变量
Person text4() {
	Person s1;
	cout << (int*)&s1 << endl;
	return s1;
}
void text5() {
	Person s2;
	s2 = text4();
	cout << (int*)&s2 << endl;
}
int main() {
	text5();
	return 0;
}

拷贝构造函数与构造函数的相同点与不同点

首先它俩都是构造函数,没有返回值,函数值为类名

构造函数时在创建对象的时候对其成员变量进行赋值操作。拷贝构造函数是用一个已经存在的对象去初始化一个新的对象。

注意:构造函数是赋值,拷贝构造是初始化。

5.构造函数的调用规则

!!默认条件下,c++编译器会给一个类至少提供4个函数

1.默认构造函数(无参,无体)

2.默认析构函数(无参,无体)

3.默认拷贝构造函数,对成员变量进行浅拷贝(值拷贝)

4.赋值运算符重载函数(浅拷贝模式)

规则:

如果用户定义了有参构造函数,系统将不会提供无参构造函数,但是系统会提供默认的拷贝函数。

如果用户定义了拷贝构造函数,系统将不会提供其他构造函数。

image-20231208203232578

6.浅拷贝

什么是浅拷贝?

拷贝的过程就是普通的传值过程,不会在堆区中额外申请空间

通过拷贝构造,不同对象的指针类型的成员变量指向的都是同一块内存,修改或删除其中一个对象的指针变量,会对其他对象造成影响。

#include <iostream>

using namespace std;
class Person {
public:
	int *p=NULL;
	int b;
	char c;
public:
	Person(int _a,int _b,char _c) {
		if (_a > 0) {
			p = new int[_a];
		}
		this->b = _b;
		this->c = _c;
	}
	Person(const Person& other) {//浅拷贝
		this->p = other.p;//普通的传值过程
		this->c = other.b;
		this->c = other.c;
	}
	~Person() {
		delete[]p;
	}

};
int main() {
	Person s1(3,2,'a');//如果用户定义了拷贝构造,编译器将不会提供其他
	Person s2(s1);
	return 0;
}

浅拷贝构造在有指针类型的成员变量的时候,很容易出错

调用析构函数的时候会导致一块区域被释放两次,编译器会报错!!!!!

image-20231208210544349

7.深拷贝

什么是深拷贝?

拷贝的过程不单单只是传值的过程了,如果有指针类型的成员变量的话,要给它在堆区重新开辟空间,不要使用已经存在的对象的指针变量所指向的堆区空间了

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

深拷贝构造的代码实现:

Person(const Person& other) {
	this->n = other.n;
	this->p = new int[n];
	for (int i = 0; i < n;i++) {
		this->p[i] = other.p[i];
	}
	this->b = other.b;
	this->c = other.c;
}

关于深拷贝和浅拷贝的总结

如果成员变量在堆区开辟空间了就不要使用编译器提供的拷贝构造函数了,因为它是浅拷贝,只是传值。

我们要自己定义一个拷贝构造函数,给成员变量重新开辟空间,重新赋值。

这样可以防止浅拷贝带来的问题

冒泡排序(BubbleSort)稳定

普通冒泡排序

算法思路:

1.每走一趟把最大的放在后面

2.第1趟,需要比较n-1次;第2趟,需要比较n-2次;第i趟,需要比较n-i次;第n-1趟,需要比较1次

3.外层循环为趟数,内层循环为比较的次数

9 8 7 6 5 4 3 2 1 0从小到大排序

算法实现:

void _BubbleSort(int nums[]) {
	for (int i = 1; i <= nums[0] - 1;i++) {
		for (int j = 1; j <= nums[0] - i;j++) {
			if (nums[j]>nums[j+1]) {
				swap(nums[j], nums[j + 1]);
			}
		}
	}
}

优化过后的冒泡排序

为什么要优化?

因为很可能在没开始比较之前序列就已经有序了,那么就没必要继续循环了

或者在循环若干次之后,发现序列有序了,没必要再循环下去

代码实现:

void BubbleSort(int nums[]) {
	for (int i = 1; i <= nums[0] - 1; i++) {
		bool flag = 0;//每走一趟都要看看它的是否已经有序
		for (int j = 1; j <= nums[0] - i; j++) {
			if (nums[j] > nums[j + 1]) {
				flag = 1;
				swap(nums[j], nums[j + 1]);
			}
		}
		if (!flag) break;
	}
}

冒泡排序的时间复杂度

最坏时间复杂度为:O(n^2)

序列都是逆序排列的

第1趟 n-1次

第2趟 n-2次

第i 趟 n-i次

第n-1趟 1次

ASL(平均查找长度)=n*(n-1)/2次

最好时间复杂度为:O(n)

序列刚开始就是有序的

那么进去走了第一趟之后发现flag为0,没被交换过,直接结束比较了

ASL=n次

平均时间复杂度为:O(n^2)

空间复杂度:O(1)

选择排序(SelectSort)不稳定

算法思路:走一趟,标记好最大数字的下标,然后和后面的交换

算法实现:

void SelectSort(int nums[]) {
	for (int i = 1; i <= nums[0] - 1;i++) {
		int index = 1;
		for (int j = 2; j <= nums[0] - i + 1;j++) {
			if (nums[index]<nums[j]) {
				index = j;
			}
		}
		swap(nums[index], nums[nums[0] - i + 1]);
	}
}

平均时间复杂度为:O(n^2)

空间复杂度为:O(1)

0.初始化列表是在构造函数中使用的

1.初始化列表的格式

初始化列表与构造函数紧密相关的

构造函数参数列表的后面加上:成员变量1(参数列表的值),成员变量2(参数列表的值)…

A(int a, int b, int c) :_b(b),_a(_b), _c(c) {//初始化列表
	cout << _a << " " << _b << " " << _c << endl;
}

2.初始化列表的作用?

1.给父类继承过来的成员变量进行初始化

当父类没有无参构造时,需要通过父类名调用父类的构造函数

2.给本类的成员变量进行初始化

非类成员,通过成员名(值)初始化

类成员,通过对象调用构造函数

#pragma once
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
class A{
    int a1;
    int a2;
public:
    A(int _a1,int _a2){
        a1=_a1;
        a2=_a2;
    }
};
class B:public A{
    int b1;
    A a3;
public:
    B(int _a1,int _a2,int _b1,int _a31,int _a32):
    //初始化列表-->1.给继承过来的父类的成员变量初始化,必须通过父类名显示调用
    //            2.给本类下的成员变量进行初始化,类成员通过对象名括号调用构造函数
    A(_a1,_a2),a3(_a31,_a32){
        b1=_b1;
    }
};
void text2_01(){
    B b(1,2,3,4,5);
    //调用无参构造函数不用加括号
} 

3.为什么要使用初始化列表?

原因1:const类型的成员变量和引用类型的成员变量必须初始化,而构造函数是在创建对象的时候对成员变量进行赋值,不是初始化,这样在编译的时候就会报错

原因2:类成员,比如说在类A里面定义类B的成员,如果类B有构造函数的话,系统将不会提供默认无参构造函数,那么如果不用初始化列表的话就会报错。

原因3:使用初始化列表会比正常赋值高效一些

4.始化列表特点

a.只能在构造函数中使用

因为对象的初始化只发生在对象创建的时候,而对象创建的时候会调用构造函数

构造函数的执行又俩个阶段

1.初始化阶段:无论成员变量是否在初始化列表中,都会进行初始化

2.赋值阶段:根据函数体里面内容进行赋值

b.成员变量初始化的顺序只于它在类中声明的顺序有关,与初始化列表的顺序无关

int _a;
int _b;
int _c;
A(int a, int b, int c) :_b(b),_a(_b), _c(c) {//初始化列表
	cout << _a << " " << _b << " " << _c << endl;
}
A(1, 2, 3);

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

c.const常量、引用和类成员必须在初始化列表中初始化

因为他们三必须创建的时候就进行初始化!!!

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

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

首先const类型的变量和引用类型都必须进行初始化操作,否则会报错

d.类对象做类成员

B中有A类成员,要先创建B中的成员变量,调用B的构造函数,然后再对B中的成员变量进行赋值

先调用类成员的构造函数,再调用本类的构造函数,最后先调用本类的析构函数,再调用类成员的析构函数

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

#include <iostream>

using namespace std;
class A {
	int a;
	int b;
	int c;
public:
	A(int _a, int _b, int _c) {
		a = _a;
		b = _b;
		c = _c;
		cout << "调用A的构造函数" << endl;
	}
	~A() {
		cout << "调用A的析构函数" << endl;
	}
};
class B {
	int x1;
	int x2;
	const int cn;
	int& m;
	A s;
public:
	B(int _x1,int _x2,int _cn, int _m, int _a, int _b, int _c) :cn(_cn), m(_m), s(_a, _b, _c) {
		x1 = _x1;
		x2 = _x2;
		cout << "调用B的构造函数" << endl;
	}
	~B() {
		cout << "调用B的析构函数" << endl;
	}
};

int main() {
	int x = 1;
	B b(6,7,1, x, 2, 3, 4);
	return 0;
}

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

1.静态成员变量

格式:类内->在成员变量的类型前面加一个static

静态成员变量只能初始化一次

它存储在静态区,静态成员变量不占用对象的空间,在编译阶段分配内存,也就是主函数之前

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

静态成员变量属于类不属于对象,多个对象共享一个共有的静态成员变量

必须在类内定义,类外初始化

不能调用构造函数初始化,因为构造函数归类所有,不是对象所有

可以通过对象名或者类名直接访问静态成员变量

//对象名.静态成员变量
//类名::静态成员变量

静态成员变量在子类和父类共享静态成员变量

在主函数之前执行的有什么?

静态成员变量的初始化和全局变量的初始化

#include<iostream>

using namespace std;
class B {
public:
	B() {
		cout << "B的构造函数" << endl;
	}
};
class A {
	int a;
public:
	static B m;//静态的类成员
	static int n;
	static void Show() {
		cout << "hello" << endl;
	}
};
int A::n = 0;//类外初始化
B A::m=B();
/*
* 1.静态成员变量属于类,不属于对象,多个对象共享一个静态成员变量
* 2.静态成员变量在类内定义,类外初始化
* 3.可以通过对象名或类名直接访问公有的静态成员变量
* 4.静态成员变量在编译阶段分配内存,也就是主函数之间
* 5.主函数之前会执行什么?
* 静态成员变量的初始化和全局变量的初始化
*/
int main() {
	cout << "__________________________" << endl;
	A a1,a2,a3;
	a1.n++;
	a2.n++;
	a3.n++;
	A::n++;
	cout << A::n << endl;
	cout << a1.n << endl;
	cout << a2.n << endl;
	cout << a3.n << endl;
	return 0;
}

2.静态成员函数

成员函数存储在代码段内,不占用对象空间

静态成员函数属于类,不属于对象

可以通过类名和对象名去调用公有的静态成员函数

静态成员函数没有this指针,只能访问静态成员

因为没有this指针的话,编译器就无法确定究竟是对哪个对象的非静态成员变量进行操作

image-20231215191233091

成员函数不占用对象的空间,成员函数是统一放在代码段当中的,当对象想访问它的成员函数的时候,会到代码段中去寻找那段代码。成员函数通过this指针去区分究竟对哪个对象里面的数据进行操作

image-20231215190423890
#include <iostream>
 
using namespace std;
class A {
	int b;
	static int a;
public:
	static void func1() {

	}
	static void func() {
		this->b = 1;
		func1();
		a = 2;
	}
};
int A::a = 0;

int main() {

	return 0;
}

3.在C++中空指针是可以访问成员函数的

#include <iostream>

using namespace std;

class A {
	int a = 2;
public:
	void func1() {
		a = 1;
		cout << "1" << endl;
	}
	static void func2() {
		cout << "2" << endl;
	}
};
int main() {
	A* a = nullptr;
	return 0;
	a->func1();
	a->func2();
	A::func2();
}

但是要注意有没有用到this指针,因为空指针调用成员函数的时候this指针也为空,对象里面是没有内容的。

为了保持程序的健壮性,可以对成员函数进行判空操作

if(this==NULL) return ;

4.静态成员函数与常函数中this指针的区别

静态成员函数:没有this指针,无法访问对象,所以也不能访问对象里面的非静态成员变量

常函数:有this指针,但this指针的类型是一个常量指针常量,无法修改对象里面成员变量的值

5.为什么要有this指针?

—————————————————————————————————————————————————————

6.空类的大小为1字节

因为有类型的内存叫类,所以要是类里面啥也没有就给它一个1字节大小

1.常对象

格式:const Type a;//常对象

性质:

1.因为有const,所以常对象里面的成员变量的值不能修改

2.常对象只能调用常函数

3.非常对象优先调用非常函数,若无非常函数,再调用常函数

2.常函数

格式:在成员函数参数列表的后面加上一个const

void get_nums() const {
		this->a = 2;
}

性质:

1.常函数有this指针,但是类型是常指常类型,无法修改对象里面的成员变量的值,也不能调用非常函数

2.常函数与非常函数会发生函数重载

如果有俩个函数,它的函数名和参数均相同,什么情况下会发生函数重载?

在这俩个函数一个是常函数,一个是非常函数的时候会发生函数重载,因为编译器会根据对象是否是常对象,来调用不同的函数。

3.成员变量在声明的时候前面加mutable,就可以在常函数中修改其值

4.常函数只能再类中定义,出了类定义是错误的

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

5.常函数不能调用非常函数,因为常函数的this指针为常指常,而非常函数的this指针为指常,类型不兼容

#include <iostream>

using namespace std;
class Person {
	mutable int a;
	char b;
public:
	Person() {
		cout << "构造函数" << endl;
	}
	~Person() {
		cout << "析构函数" << endl;
	}
	void get_nums() {
		this->a = 2;
	}
	void get_nums() const {
		this->a = 2;
	}
	void get_nums2() {//非常函数
		//this指针是一个指针常量-->Type* const this(指向不能改变,但是可以修改里面的内容)
		a = 2;
	}
	void get_nums2cst() const {//常函数
		//this指针是一个常量指针常量-->const Type* const this (指针的指向和指向对象里面的内容都不可以改变)
		a = 2;//常函数,不能修改成员变量的值
	}
};
void show() const {//常函数只能再类中定义
//错误!
}
int main() {
	Person p1;//非常对象
	const Person p2;//常对象-->对象里面的成员变量的值不可以修改
	p1.get_nums2();//非常对象调用非常函数
	p1.get_nums2cst();//非常对象调用常函数
	p2.get_nums2cst();//常对象调用常函数
	p2.get_nums2();//常对象不可以调用非常函数
	/*
	* 总结:常对象只能调用常函数,非常对象优先调用非常函数,没有非常函数再调用常函数
	*/
	p1.get_nums();
	p2.get_nums();//函数的重载,编译器可以通过调用对象的不同来区分调用常函数还是非常函数
	return 0;

<注意>:如果函数名一样,参数列表也一样,也有可能发生函数重载,因为有常函数和非常函数,编译器通过常对象和非常对象来区分

3.常函数、非常函数与静态成员函数中this指针的区别

在非常函数中:this指针是一个指针常量,指向不可以改变

在常函数中:this指针是一个常量指针常量,指向和指向里面的内容均不可以改变,这也就是为什么常函数里面不可以修改成员的是属性。

静态成员函数中:根本没有this指针,所以无法修改对象里面成员变量的值

1.为什么要有友元?

一个类外函数(全局函数)或者类想访问另一个类私有的成员属性的时候,就要用到友元

关键词:friend

2.使用友元的三种情况

1.全局函数做友元

告诉我想访问的那个类,我是你们这个类的我的好朋友,可以让我访问私有成员属性

#include <iostream>

using namespace std;
class Building {
	friend void GoodFreind(Building& b);
	string bedroom;//私有的
public:
	string sittingroom;
	Building();//类内声明构造函数
	/*{
		this->bedroom = "卧室";
		this->sittingroom = "客厅";
	}*/
};
Building::Building() {
	this->bedroom = "卧室";
	this->sittingroom = "客厅";
}
void GoodFreind(Building& b) {//当全局函数想访问一个类中的私有成员属性的时候,把这个函数声明成友元函数就可以实现了
	cout << "朋友相进入" << b.sittingroom << endl;
	cout << "朋友想进入" << b.bedroom << endl;
}

int main() {
	Building b;
	GoodFreind(b);
	return 0;
}

2.类做友元

友元除了前面讲过的函数以外,友元还可以是类,即一个类可以作另一个类的友元。当一个类作为另一个类的友元时,这就意味着这个类的所有成员函数都是另一个类的友元函数。

当一个函数是另外一个类的友元函数的时候,那么它就可以访问另一个类的私有成员变量

#include <iostream>

using namespace std;

class Building {
	friend Friend;//Friend类设为友元
	string bedroom;//私有的
public:
	string sittingroom;
	Building();//类内声明构造函数
};
class Friend {
	Building *b;
public:
	Friend();//类内声明构造函数
	~Friend();
	void vist();7
};
Building::Building() {
	this->bedroom = "卧室";
	this->sittingroom = "客厅";
}
Friend::Friend(){
	b = new Building();//成员变量指向堆区空间了,要调用析构函数
}
Friend::~Friend() {
	if(b) delete b;
}
void Friend::vist() {//一个类想访问另一个类的私有成员,把这个类变为友元
	cout << "朋友想进入" << b->sittingroom;
	cout << "朋友想进入" << b->bedroom;
}
int main() {
	Building b;
	return 0;
}

3.类中的成员函数做友元

#include <iostream>

using namespace std;
class Friend {
	Building* b;
public:
	Friend();//类内声明构造函数
	~Friend();
	void vist();
};

class Building {
	friend void Friend::vist();//Friend类里面的成员函数设为友元
	string bedroom;//私有的
public:
	string sittingroom;
	Building();//类内声明构造函数
};
class A{
public:
    void visit(); 
};
Building::Building() {
	this->bedroom = "卧室";
	this->sittingroom = "客厅";
}
Friend::Friend(){
	b = new Building();//成员变量指向堆区空间了,要调用析构函数
}
Friend::~Friend() {
	if(b) delete b;
}
void Friend::vist() {//一个类想访问另一个类的私有成员,把这个类变为友元
	cout << "朋友想进入" << b->sittingroom;
	cout << "朋友想进入" << b->bedroom;
}
int main() {
	Building b;
	return 0;
}

3.友元的好坏

好处:可以访问类中的私有成员

坏处:破坏了封装性

访问一个类的私有成员变量由俩种方法

1.对象调用get set函数

2.把一个类和全局函数设置为友元

运算符的重载有俩种形式,一种类内(成员函数)重载运算符,一种类外(全局函数)重载运算符,类内可以少传递一个参数,但是不是所有运算符重载都可以再类内中进行的,比如<< >>

1.重载’+'运算符

值返回

为什么要重载加法运算符?

因为C++提供的加法运算符只能满足基本数据类型间的加法,如果我想让俩个相同的类的对象进行加法的话会报错

image-20231218154406227

所以为了能让俩个相同类的对象进行加法,我们要把这个过程封装到一个函数里面,只不过要多加一个关键字operator而已,让编译器一下子就找到,这个是重载运算符的函数

作用:实现俩个自定义运算符相加

成员函数实现运算符重载

可以少传递一个参数

#include <iostream>

using namespace std;

class Box {
	int length;
	int width;
	int height;
public:
	Box() {
		length = 0;
		width = 0;
		height = 0;
	}
	Box(int length,int width,int height) {
		this->length = length;
		this->width = width;
		this->height = height;
	}
	Box(const Box& other) {
		length = other.length;
		width = other.width;
		height = other.height;
	}
	//成员函数重载加法运算符
	Box operator+(const Box& other) {
		Box p;
		p.length = this->length + other.length;
		p.width = this->width + other.width; 
		p.height = this->height + other.height;
		return p;
	}
};
int main() {
	Box a(1,2,3);
	Box b(2, 3, 4);
	Box c = a + b;//直接调用
	Box d;
	d = a.operator+(b);//调用重载运算符的函数
	return 0;
}

全局函数实现运算符的重载

#include <iostream>

using namespace std;

class Box {
	int length;
	int width;
	int height;
	friend Box operator+(const Box& other1, const Box& other2);
	friend Box operator+(const Box& other1, int val);
public:
	Box() {
		length = 0;
		width = 0;
		height = 0;
	}
	Box(int length,int width,int height) {
		this->length = length;
		this->width = width;
		this->height = height;
	}
	Box(const Box& other) {
		length = other.length;
		width = other.width;
		height = other.height;
	}
};
//全局函数重载加法运算符
Box operator+(const Box& other1,const Box& other2) {//不调用成员函数是无法访问私有的成员变量的,需要设置为友元,告诉编译器,我这个重载运算符的函数是你这个类的好朋友,都哥们,能f
	Box p;
	p.length = other1.length + other2.length;
	p.width = other1.width + other2.width;
	p.height = other1.height + other2.height;
	return p;
}
Box operator+(const Box& other1,int val) {
	Box p;
	p.length = other1.length + val;
	p.width = other1.width + val;
	p.height = other1.height + val;
	return p;
}
int main() {
	Box a(1,2,3);
	Box b(2, 3, 4);
	Box c = a + b;
	Box d;
	d=operator+(a,b);
	return 0;
}

2.重载’+=’ 运算符

引用返回

#include <iostream>

using namespace std;
class Box {
	int length;
	int width;
	int high;
	friend Box& operator+=(Box& other1, Box& other2);
public:
	Box() {
		length = 1;
		width = 2;
		high = 3;
	}
	/*Box& operator+=(const Box& other) {
		this->length += other.length;
		this->width += other.width;
		this->high += other.high;
		return *this;
	}*/
	int get_length() {
		return this->length;
	}
};
Box& operator+=(Box& other1,const Box& other2) {
	other1.length += other2.length;
	other1.width += other2.width;
	other1.high += other2.high;
	return other1;
}
int main() {
	Box a, b,c;
	a += b+=c;//隐式调用函数
	cout << a.get_length();
	return 0;
}

3.重载’<<'运算符

引用返回

cout是ostream类的对象

cin是istream类的对象

#include <iostream>

using namespace std;
class Box {
	int length;
	int width;
	int high;
	friend ostream& operator<<(ostream& o, const Box& b);
public:
	Box() {
		length = 1;
		width = 2;
		high = 3;
	}
};
ostream& operator<<(ostream& o,const Box& b) {
	o << b.length << ' ' << b.width << ' ' << b.high << endl;
	return o;
}
int main() {
	Box a,b,c;
	//cout << a;没有与这些操作数相匹配的运算符
	/*
	* 你想重载一个运算符要么在类内重载,要么在类外重载
	* 但是cout对象属于ostream类,该类我们无法修改,所以只能在类外用全局函数重载
	*/
	cout << a << b << c;
	return 0;
}

4.重载’>>'运算符

引用返回

#include <iostream>

using namespace std;
class Box {
	int length;
	int width;
	int high;
	friend ostream& operator<<(ostream& o, const Box& b);
	friend istream& operator>>(istream& i, Box& b);
public:
	Box() {
		length = 1;
		width = 2;
		high = 3;
	}
};
ostream& operator<<(ostream& o,const Box& b) {
	o << b.length << ' ' << b.width << ' ' << b.high << endl;
	return o;
}
istream& operator>>(istream& i,Box& b) {
	i >> b.length;
	i >> b.width;
	i >> b.high;
	return i;
}
int main() {
	Box a, b,c;
	//cout << a;没有与这些操作数相匹配的运算符
	/*
	* 你想重载一个运算符要么在类内重载,要么在类外重载
	* 但是cout对象属于ostream类,该类我们无法修改,所以只能在类外用全局函数重载
	*/
    cin >> a>>b>>c;
    cout << a << b << c;
	return 0;
}

5.重载’++'运算符

前加加–>引用返回

后加加–>值返回

#include <iostream>
using namespace std;
class A {
	//private 类内 
	int a;
	int b;
	int c;
	friend ostream& operator<<(ostream& o, const A& other);
public:
	A() {
		this->a = 0;
		this->b = 0;
		this->c = 0;
	}
	A& operator++() {//前加加
		a++;
		return *this;
	}
	A operator++(int) {//后加加
		//不要返回局部变量的引用
		A t = *this;
		a++;
		return t;
	}
};
//
ostream& operator<<(ostream& o, const A& other) {
	o << other.a << endl;
	return o;
}
int main() {
	A s;
	cout << s++;
	cout << ++s;
	return 0;
}

6.重载’比较’运算符

#include <iostream>
#include <iostream>
using namespace std;

class Person {
	string name;
	int age;
public:
	Person(string name, int age) {
		this->name = name;
		this->age = age;
	}
	bool operator==(const Person& other) const{
		if (this->age == other.age) {
			return 1;
		}
		return 0;
	}
	bool operator>(const Person& other) const{
		if (this->age > other.age) {
			return 1;
		}
		return 0;
	}
	bool operator<(const Person& other) const{
		if (this->age < other.age) {
			return 1;
		}
		return 0;
	}
	bool operator!=(const Person& other) const{
		if (this->age != other.age) {
			return 1;
		}
		return 0;
	}
};
int main() {
	Person p0("施易辰", 20);
	Person p1("田雪嵩", 100);
	if (p0 == p1) {
		cout << "年龄相等" << endl;
	}
	else if (p0 > p1) {
		cout << "p0大于p1" << endl;
	}
	else if (p0 < p1) {
		cout << "p0小于p1" << endl;
	}
	else
		cout << "p0不等于p1" << endl;
	return 0;
}

7.重载’='赋值运算符

引用返回

编译器自动提供的赋值运算符是浅拷贝类型,如果成员变量指向堆区空间的话,会连续俩次析构函数会报错

#include <iostream>
#include <iostream>
using namespace std;

class Person {
	string name;
	int age;
	int* password;
	friend ostream& operator<<(ostream& o, const Person& p);
public:
	Person(string name, int age, int mm) {
		this->name = name;
		this->age = age;
		this->password = new int[10];
		*password = mm;
	}
	~Person() {
		if (password) delete[]password;
	}
};
ostream& operator<<(ostream& o, const Person& p) {
	o << p.name << ' ' << p.age << *(p.password) << endl;
	return o;
}
int main() {
	Person p0("施易辰", 20, 12345);
	Person p1("田雪嵩", 100, 123425);
	p0 = p1;//编译器自动提供的是浅拷贝类型的赋值运算符 成员变量指向堆区空间时候会报错
	cout << p0 << p1;
	return 0;
}
image-20231224131403075

所以如果成员变量指向堆区空间的话要自己重载赋值运算符,类型为深拷贝模式

#include <iostream>
#include <iostream>
using namespace std;

class Person {
	string name;
	int age;
	int* password;
	friend ostream& operator<<(ostream& o, const Person& p);
public:
	Person(string name, int age, int mm) {
		this->name = name;
		this->age = age;
		this->password = new int[10];
		*password = mm;
	}
	~Person() {
		if (password) delete[]password;
	}
	Person& operator=(const Person& other) {//深拷贝模式的赋值运算符
		this->name = other.name;
		this->age = other.age;
		this->password = new int[10];
		*password = *(other.password);
		return *this;
	}
};
ostream& operator<<(ostream& o, const Person& p) {
	o << p.name << ' ' << p.age << *(p.password) << endl;
	return o;
}
int main() {
	Person p0("施易辰", 20, 12345);
	Person p1("田雪嵩", 100, 123425);
	p0 = p1;//调用自定义的赋值运算符的重载函数
	cout << p0 << p1;
	return 0;
}
image-20231224131600567

不会报错!

8.重载’()'运算符

又称仿函数

#include <iostream>

using namespace std;

class Myprint {
public:
	void operator()(string text) {
		cout << text << endl;
	}
};
class Myadd {
public:
	int operator()(int a, int b) {
		return a + b;
	}
};
void text01() {
	Myprint myprint;
	myprint("abc");
	myprint.operator()("dce");
}
void text02() {
	int a = 1;
	int b = 2;
	Myadd myadd;
	cout << myadd.operator()(1, 2) << endl;
	cout << myadd(3, 4) << endl;
}
int main() {
	text01();
	text02();
	return 0;
}

main.c

#include "List.h"
const int N=1e5+10;
int nums[N],n;
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>nums[i];
    }
    List L(nums,n);
    L.List_Print();
    return 0;
}

List.h

#include <iostream>
using namespace std;

struct LNode{
    int data;
    LNode* next;
    LNode(int data){//创建结点时候的构造函数,实体实现是把它的数据域赋值,把他的指针域设置为空
        this->data=data;
        this->next= nullptr;
    }
};
class List{
    LNode* Head=nullptr;//头指针
    LNode* Tail=nullptr;//尾指针
public:
    List(int nums[],int n);//尾插法创建单链表的声明
    void List_Print();//打印链表中的数据域
    ~List();//链表的析构函数
};

List.cpp

#include "List.h"

List::List(int nums[],int n) {//尾插法创建带链表的具体实现
    Head=new LNode(nums[1]);
    Tail=Head;
    for(int i=2;i<=n;i++){
        LNode* nwNode=new LNode(nums[i]);
        Tail->next=nwNode;
        Tail=Tail->next;
    }
}
void List::List_Print() {
    LNode* p=Head;
    while(p){
        cout<<p->data<<" ";
        p=p->next;
    }
    cout<<endl;
}
List::~List() {
    LNode* p=Head;
    while(p){
        cout<<p->data<<"析构了"<<endl;
        Head=p->next;
        delete p;
        p=H;
    }
    cout<<"全部析构成功了"<<endl;
}

一般.h文件中不存放代码的实现,而是代码的声明

.cpp文件中用来写代码的实现

没给头结点删除中间结点

算法思想:把删除结点的数据域值改为该结点后继结点数据域的值跨过后继结点直接相连后继的后继

image-20240110220940674
class Solution {
public:
    void deleteNode(ListNode* node) {
        node->val=node->next->val;
        node->next=node->next->next;
    }
};

删除链表中指定元素

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

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* xhead=new ListNode(0,head);
        ListNode *slow,*fast;
        slow=xhead;
        fast=head;
        while(fast){
            if(fast->val==val){
            slow->next=fast->next;
            fast=slow->next;
            }
            else{
            slow=slow->next;
            fast=fast->next;
            }
        }
        return xhead->next;
    }
};

习题:【腾讯文档】算法题大纲
https://docs.qq.com/doc/DTnFNVERRWFNqaXJD

1.继承的基本语法

class 类名:继承权限 父类{

}

class A{
	int a;
	int b;
	int c;
};
class B:public A{
	//在子类中,abc已经被继承过来了,不用写重复的代码
};

A被称为父类、基类

B被称为子类、派生类

继承的作用:可以减少重复的代码

2.继承权限

和访问权限一样,同样有三种

第一种private继承权限

#pragma once
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
class father{
private:
    int pri;
    void prifun();
protected:
    int pro;
    void profun();
public:
    int pub;
    void pubfun();
};
class son:private father{//继承权限作用:缩小父类成员在子类中的访问权限,只会缩小不会扩大
    /*
     * 私有的继承权限,缩小了子类的访问权限
     * 成员变量pro和成员函数profun都变成私有的了,在grandson中可以看出
     * 成员变量pub和成员函数pubfun都变成私有的了,
     */
    void fun(){
        pri=1;//错误
        prifun();//错误,父类中私有的成员子类都不可以访问
        pro=1;//父类中受保护的和共有的成员子类可以访问
        profun();
        pub=1;
        pubfun();
    }

};
class grandson:public son{
    void fun(){
        pro=1;//错误
        profun();//错误
        pub=1;//错误
        pubfun();//错误
    }
};
void text01(){
    grandson s;
    s.pub=1;//错误
}

第二种protect继承权限

#pragma once
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
class father{
private:
    int pri;
    void prifun();
protected:
    int pro;
    void profun();
public:
    int pub;
    void pubfun();
};
class son:protected father{//继承权限作用:缩小父类成员在子类中的访问权限,只会缩小不会扩大
    void fun(){
        pri=1;//错误
        prifun();//错误,父类中私有的成员子类都不可以访问
        pro=1;//父类中受保护的和共有的成员子类可以访问
        profun();
        pub=1;
        pubfun();
    }

};
class grandson:public son{
    void fun(){
        pro=1;
        profun();
        pub=1;
        pubfun();
    }
};
void text01(){
    grandson s;
    s.pub=1;//错误
}

第三种public继承权限

#pragma once
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
class father{
private:
    int pri;
    void prifun();
protected:
    int pro;
    void profun();
public:
    int pub;
    void pubfun();
};
class son:public father{//继承权限作用:缩小父类成员在子类中的访问权限,只会缩小不会扩大
    /*
     * 私有的继承权限,缩小了子类的访问权限
     * 成员变量pro和成员函数profun都变成私有的了,在grandson中可以看出
     * 成员变量pub和成员函数pubfun都变成私有的了,
     */
    void fun(){
        pri=1;//错误
        prifun();//错误,父类中私有的成员子类都不可以访问
        pro=1;//父类中受保护的和共有的成员子类可以访问
        profun();
        pub=1;
        pubfun();
    }

};
class grandson:public son{
    void fun(){
        pro=1;
        profun();
        pub=1;
        pubfun();
    }
};
void text01(){
    grandson s;
    s.pub=1;//可以
}

通过上面三段代码我们可以看出

父类中如果有private类型的成员,无论以何种继承方式,都无法在子类中访问

虽然是无法访问,但是它仍旧被继承了过来,只是被隐藏了,仍然占用子类的空间

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

3.继承中的构造析构顺序

构造函数

当创建子类对象的时候,会优先调用父类的构造函数,然后调用子类的构造函数

为什么?

因为子类对象需要先继承父类的成员变量,也就是先创建父类的成员变量,然后要对父类对象赋值,所以调用父类构造函数,然后再创建自己的成员变量,调用自己的构造函数

析构函数

入栈出栈顺序有关

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

#pragma once
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
class father{
public:
    father(){
        cout<<"father构造"<<endl;
    }
    ~father(){
        cout<<"father析构"<<endl;
    }
};
class son:public father{
public:
    son(){
        cout<<"son构造"<<endl;
    }
    ~son(){
        cout<<"son析构"<<endl;
    }
};
class grandson:public son{
public:
    grandson(){
        cout<<"grandson构造"<<endl;
    }
    ~grandson(){
        cout<<"grandson析构"<<endl;
    }
};
void text01(){
    grandson gr;
}

4.继承中的同名函数问题

父类与子类有同名成员的时候不会产生二义性,会隐藏父类继承过来的成员

要想访问父类的同名成员,必须利用作用域

#pragma once
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
class father{
public:
    father(){
        cout<<"father构造"<<endl;
    }
    void fun(){
        cout<<"father的fun函数"<<endl;
    }
};
class son:public father{
public:
    son(){
        cout<<"son构造"<<endl;
    }
    void fun(){
        cout<<"son的fun函数"<<endl;
    }
};
class grandson:public son{
public:
    grandson(){
        cout<<"grandson构造"<<endl;
    }
    void fun(){
        cout<<"grandson的fun函数"<<endl;
    }
};
void text01(){
    father f;
    son s;
    grandson gr;
    f.father::fun();
    s.son::fun();
    gr.grandson::fun();

}

5.继承中的同名静态成员变量问题

静态成员变量俩种调用方式,作用域和对象名调用

但是无论是哪种调用,再同名静态成员变量继承中,都要加上作用域

#pragma once
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
class father{
public:
    static int a;
};
int father::a=1;
m
class son:public father{
public:
    static int a;
};
int son::a=2;

class grandson:public son{
public:
    static int a;
};
int grandson::a=3;
void text01(){
    grandson gr;
    cout<<gr.father::a<<endl;//对象调用
    cout<<gr.son::a<<endl;
    cout<<gr.a<<endl;
    cout<<father::a<<endl;//作用域调用
    cout<<son::a<<endl;
    cout<<grandson::a<<endl;
}

6.多继承

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

多继承构造函数的调用顺序与继承的顺序有关,谁写再前面先继承谁

#pragma once
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
class A{
public:
    int a;
    A(){
        cout<<"A构造"<<endl;
    }
};

class B{
public:
    int a;
    B(){
        cout<<"B构造"<<endl;
    }
};

class C:public B,public A{
public:
    C(){
        cout<<"C构造"<<endl;
    }
};
void text01(){
    C c;
    c.B::a;//多继承问题中会存在二义性-->解决方式,作用域
    c.A::a;
}

7.菱形继承

同样存在二义性但是与多击沉不同可以通过virtual来解决,也可以通过作用域解决

#pragma once
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
class A{
public:
    int a;
    A(){
        cout<<"A构造"<<endl;
    }
};

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

class C:virtual public A{
public:
    C(){
        cout<<"C构造"<<endl;
    }
};
class D:public B,public C{
public:
    D(){
        cout<<"D构造"<<endl;
    }
};
void text01(){
    D d;
    d.B::a;
    d.a;
}

初始化列表的作用

1.给父类继承过来的成员变量进行初始化

当父类没有无参构造时,需要通过父类名调用父类的构造函数

2.给本类的成员变量进行初始化

非类成员,通过成员名(值)初始化

类成员,通过对象调用构造函数

#pragma once
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
class A{
    int a1;
    int a2;
public:
    A(int _a1,int _a2){
        a1=_a1;
        a2=_a2;
    }
};
class B:public A{
    int b1;
    A a3;
public:
    B(int _a1,int _a2,int _b1,int _a31,int _a32):
    //初始化列表-->1.给继承过来的父类的成员变量初始化,必须通过父类名显示调用
    //            2.给本类下的成员变量进行初始化,类成员通过对象名括号调用构造函数
    A(_a1,_a2),a3(_a31,_a32){
        b1=_b1;
    }
};
void text2_01(){
    B b(1,2,3,4,5);
    //调用无参构造函数不用加括号
} 

运算符重载

1.+运算符和后++运算符函数重载是值返回,不是引用返回

因为这俩种运算符重载再函数内都创建了新对象,且想返回这个对象

显然不能返回局部对象的引用,所以只能值返回,通过拷贝一个匿名对象来解决声明周期消失的问题

调用无参构造不需要加括号

函数重写(覆盖)

1.定义:在继承过程中,子类重新定义父类的虚函数,该虚函数具有和子类重写函数相同的返回值、名字、和参数

2.特点

父类必须是虚函数

虚函数和重写函数必须有相同的返回值 名字 参数

#include<iostream>

using namespace std;
class father{
public:
    virtual void fun(){
        cout<<"father的fun"<<endl;
    }
};
class son:public father{
public:
    void fun(){
        cout<<"son的fun"<<endl;
    }
};

函数隐藏

1.定义:在继承过程中,当子类和父类具有相同名字的函数时,子类会隐藏父类的函数

class father{
public:
    void fun(){
        cout<<"father的fun"<<endl;
    }
};
class son:public father{
public:
    void fun(){
        cout<<"son的fun"<<endl;
    }
};

函数重写和函数隐藏的区别

class Base{
public:
    void funA(){cout<<"Base::funA"<<endl;}
    virtual void funB(){cout<<"Base::funB"<<endl;}
};
class Heri:public Base{
public:
    void funA(){cout<<"函数隐藏Heri::funA"<<endl;}//函数隐藏
    void funA(int a){cout<<"函数隐藏Heri::funA(int a)"<<endl;}//函数隐藏
    //在同一个作用域下函数名字相同参数列表不同的俩个函数才会发生重载
    virtual void funB(){cout<<"函数重写Heri::funB"<<endl;}//函数重写
};

多态的分类

1.静态多态

在编译阶段就确定执行哪个函数(函数重载和运算符的重载都属于静态多态)

静态多态的地址早绑定,在编译阶段就确定了函数的地址

2.动态多态

在运算阶段才能确定执行哪个函数(函数的重写属于动态多态)

动态多态的地址晚绑定,在运行阶段才确定函数的地址

3.函数重载、函数覆盖(重写)、函数隐藏间的区别

函数重载:(静态多态)函数名相同,但参数列表不同,并且返回值不作为判断重载能否发生的条件

函数重写:不但函数名相同,而且返回值,参数列表都必须相同

函数隐藏:只要函数名字相同就可以

如何满足动态多态的条件?

子类重新定义父类的虚函数(虚函数与重写函数除了函数体啥啥都相同)

什么时候使用动态多态?

当父类的指针或引用指向子类对象的时候

#include<iostream>

using namespace std;
class Animal{
public:
    virtual void speak(){
        cout<<"Animal speak"<<endl;
    }
};
class cat:public Animal{
public:
    virtual void speak(){
        cout<<"cat speak"<<endl;
    }
};
class dog:public Animal{
public:
    virtual void speak(){
        cout<<"dog speak"<<endl;
    }
};
void speak(Animal& a){
    a.speak();
}
int main(){
    Animal a;
    cat c;
    dog g;
    speak(a);
    speak(c);
    speak(g);
    return 0;
}

动态多态的实现!

虚表(虚函数表)

C++为了实现运行时的多态采取的是一种动态绑定技术,它的核心就是虚表

1.当类中有虚函数的时候,就会创建虚表

2.虚表中存储的是虚函数的地址,它的本质是一个指针数组

3.虚表存储在静态区,在编译阶段分配空间,所以它的不占用对象空间,和静态成员变量一样属于类不属于对象,每个类有一个虚表,多个对象共享一份虚表

4.在继承过程中,子类会继承父类的函数调用权,肯定也会继承父类虚函数的调用权,那么子类就会拥有自己的虚表

5.虚函数的调用必须经过虚表,但是非虚函数的调用不用经过虚表

虚表指针

构造函数会给虚表指针赋值的,所以构造函数不能声明为虚函数

构造函数–>赋值虚表指针–>虚表–>虚函数,所以要没有虚表指针就无法调用虚函数,所以更不能声明为虚函数

为什么要有虚表指针?

如果每一个对象都有一个虚表的话,过于浪费空间,莫不如把虚表放在静态区,在每个对象中,放一个能指向虚表的指针,这样不但可以访问虚表,而且还节省空间,所以虚表指针就产生了

有虚函数就会有虚表,有虚表就会有虚表指针,所以如果一个类的空实现中多了一个虚函数,那么对它的对象求sizeof它的大小不会是1而是虚表指针的大小

_*vptr

当父类的引用或者指针指向子类的对象的时候

<注意>:父类指针可以指向子类对象,但是子类指针不可以指向为父类对象

#include<iostream>
#include<algorithm>
#include<map>
#include<vector>
using namespace std;
class father{
public:
};
class son:public father{
public:
};
int main(){
    father f;
    son s;
    father *p1=&s;//父类指针或引用可以指向子类对象
    //子类=父类继承的+自己的,所以子类内存空间是大于父类的
    //因为子类可以通过(屏蔽子类本身内存)强转为父类
    son *p2=&f;//子类指针或引用不可以指向父类对象
    //但是父类必须扩容,才能转化为子类,但是一扩容就会改变内存空间,所以不能转强为子类
    return 0;
}
//通过父类的引用可以找到子类的对象,然后访问的它虚表指针,通过虚表指针可以找到虚表,虚表中找到调用虚函数的地址,然后完成访问
#include<iostream>

using namespace std;
class father{
public:
    virtual void vfun1(){cout<<"father::vfun1"<<endl;}
    virtual void vfun2(){cout<<"father::vfun2"<<endl;}
    void fun1(){cout<<"father::fun1"<<endl;}
    void fun2(){cout<<"father::fun2"<<endl;}
    //_vptr虚表指针 -->虚表-->虚函数
    /*
     * _vptr-->&father::vfun1-->father::vfun1
     *         &father::vfun2-->father::vfun2
     * */
private:
    int val1;int val2;
};
class son:public father{
public:
    virtual void vfun1(){cout<<"son::vfun1"<<endl;}
    /*
     * _vptr-->&son::vfun1-->son::vfun1
     *         &father::vfun2-->father::vfun2
     * */
};
class grandson:public son{
public:
    virtual void vfun2(){cout<<"grandson::vfun2"<<endl;}
    /*
     * _vptr-->&son::vfun1-->son::vfun1
     *         &grandson::vfun2-->grandson::vfun2
     * */
};
void fun1(father& f){
    f.vfun1();
}
void fun2(father& f){
    f.vfun2();
}
int main(){
    father f;
    son s;
    grandson g;
    fun1(f);
    fun1(s);
    fun1(g);
    fun2(f);
    fun2(s);
    fun2(g);
    return 0;
}
father::vfun1
son::vfun1
son::vfun1
father::vfun2
father::vfun2
grandson::vfun2
#include<iostream>
#include<algorithm>
#include<map>
#include<vector>
using namespace std;
/*
 * 纯虚函数和抽象类
 * 语法:virtual void fun()=0
 * 含有纯虚函数的类叫抽象类,抽象类不能创建对象
 * 如果子类继承抽象类,必须重写纯虚函数,要不子类也为抽象类,无法创建对象
 * */
class father {
public:
    father(){
        cout<<"father构造"<<endl;
    }
    virtual ~father(){
        cout<<"father析构"<<endl;
    }
};
class son:public father{
public:
    son(){
        cout<<"son构造"<<endl;
    }
    ~son(){
        cout<<"son析构"<<endl;
    }
};
int main(){
    father *pf=new son;
    delete pf;
    return 0;
}

总结

当父类指针或引用指向子类对象的时候,该指针或引用只能调用父类中的非虚函数,和子类重写的虚函数

纯虚函数(抽象函数)

1.格式

virtual Type name(参数)=0;

2.没有函数的直接=0

抽象类

1.定义:含有纯虚函数的类叫抽象类

2.抽象类不能创建对象

3.抽象类一般作为基类

4.当基类为抽象类的时候,子类如果不重写基类的纯虚函数仍为抽象类

#include<iostream>

using namespace std;
class A{//含有纯虚函数的类叫抽象类
public:
    virtual void fun()=0;//纯虚函数
};
class B:public A{
    //B会继承A函数的调用权,如果不重写纯虚函数,子类为抽象类
    virtual void fun(){
        cout<<"B::fun"<<endl;
    }
};
int main(){
//    A a;抽象类无法创建对象
//    B b;子类如果不重写父类的纯虚函数,会变成抽象类,无法创建对象
    B b;//重写了父类的纯虚函数,不会变成抽象类,可以创建对象
    return 0;
}

5.抽象类具有构造函数和虚构函数

因为构造函数的作用之一是给虚表指针赋值,抽象类中有纯虚函数,所以就一定存在虚表,存在虚表就一定有虚表指针,有虚表指针就一定会有构造函数

纯虚函数(抽象函数)

1.格式

virtual Type name(参数)=0;

2.没有函数的直接=0

抽象类

1.定义:含有纯虚函数的类叫抽象类

2.抽象类不能创建对象

3.抽象类一般作为基类

4.当基类为抽象类的时候,子类如果不重写基类的纯虚函数仍为抽象类

#include<iostream>

using namespace std;
class A{//含有纯虚函数的类叫抽象类
public:
    virtual void fun()=0;//纯虚函数
};
class B:public A{
    //B会继承A函数的调用权,如果不重写纯虚函数,子类为抽象类
    virtual void fun(){
        cout<<"B::fun"<<endl;
    }
};
int main(){
//    A a;抽象类无法创建对象
//    B b;子类如果不重写父类的纯虚函数,会变成抽象类,无法创建对象
    B b;//重写了父类的纯虚函数,不会变成抽象类,可以创建对象
    return 0;
}

5.抽象类具有构造函数和虚构函数

因为构造函数的作用之一是给虚表指针赋值,抽象类中有纯虚函数,所以就一定存在虚表,存在虚表就一定有虚表指针,有虚表指针就一定会有构造函数

虚析构与纯虚虚构的作用

为了解决父类指针指向堆区中子类对象的时,释放堆区空间的问题
#include<iostream>

using namespace std;
class base{
public:
    base(){
        cout<<"base构造"<<endl;
    }
    virtual ~base(){
        cout<<"base析构"<<endl;
    }
};
class son:public base{
public:
    son(){
        cout<<"son构造"<<endl;
    }
    //在继承过程中,编译器会把子类和父类的析构函数统一看成destructor同名析构函数
    //所以会发生函数隐藏
    ~son(){
        cout<<"son析构"<<endl;
        //base::~base();
    }
};
int main(){
    base *b=new son;//父类指针指向子类对象,调用父类的非虚函数和子类重写的虚函数
    //发生函数隐藏,只会调用base析构
    delete b;
    return 0;
}

纯虚析构

既可以实现析构函数的作用还可以将该类变成抽象类,一举俩得

定义:

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

#include <iostream>

using namespace std;

inline int fun(int a,int b){
    return a+b;
}
int main(){
    int a,b;cin>>a>>b;
    int c=fun(a,b);
    cout<<c<<endl;
    return 0;
}

模板类

#include <iostream>
#include <vector>
using namespace std;
template <typename T>
//int compare(T &a,T &b){
//    if(a>b)      return 1;
//    else if(a<b) return -1;
//    else         return 0;
//}
class stack{
private:
    vector<T> vec;
public:
    void push(T &x){
        vec.push_back(x);
    }
    void pop(){
        vec.pop_back();
    }
    int top(){
        return vec.back();
    }
    bool empty(){
        return vec.size()==0? 1 : 0;
    }
};

int main(){
    stack<int> stk;
    for(int i=1;i<=5;i++){
        stk.push(i);
    }
    while(!stk.empty()){
        cout<<stk.top()<<"  ";
        stk.pop();
    }
    return 0;
}

传统方式处理错误和异常处理错误的区别

传统方式处理错误,放生错误时候会直接中断程序,而且不提示哪发生错误
但是异常处理错误,当发生错误时候会throw一个异常对象,然后被catch捕捉,父类指针或者引用指向子类对象,catch函数种的参数一般为父类引用这样就可以通过动态多态来输出究竟是哪出错了,然后提示你发生的是什么异常,而且程序不会中断会继续执行
#include <iostream>
#include <vector>

using namespace std;


int main(){
    vector<int> vec;
    try{
        vec[3];
        //但是[]不会自动抛出异常,所以catch捕捉不到,会程序中断
        //vec.at 会自动抛出异常对象
    }
    catch(const std::exception& e){
        cout<<e.what()<<endl;
    }
    cout<<"continue"<<endl;
    return 0;
}

自定义异常类

#include <iostream>
#include <vector>

using namespace std;

class MyException{
private:
    string str;
public:
    MyException(string str):str(str){}
    string what(){
        return str;
    }
};
int a_b(int a,int b){
    if(!b){
        //当发生异常的时候,创建异常对象
        MyException my("除数不能为零");
        //然后抛出异常对象
        throw my;
        cout<<"抛出异常对象后面不会执行"<<endl;
    }
    return a/b;
}
int main(){
    int a,b;cin>>a>>b;
    try{
        a_b(a,b);
    }
    catch(MyException &e){
        cout<<e.what()<<endl;
    }
    return 0;
}

语法

[capture] (params) ->ret { body; };
捕获列表 参数列表 返回值 函数体
#include <iostream>


using namespace std;
/*
 * []   不捕获任何元素
 * [&]  捕获外部作用域中所有变量,按照引用方式的捕获,可读可写
 * [=]  捕获外部作用域中所有变量,按照值方式的捕获,可读不可写
 * [=,&a]   引用捕获变量a,值捕其他变量
 * [a]  值捕获变量a,不捕获其他变量
 * */

int main(){
    int a=10,b=20;
    auto f1=[]{return a;};//不捕获任何元素
    auto f2=[&]{return a++;};//引用捕获可读可写
    auto f3=[=]{return a+b;};//值捕获可读不可写
    auto f4=[a,&b]{return a+(b++);};//值捕获变量a,引用捕获变量b
    return 0;
}
#include <iostream>


using namespace std;
class A{
    int num=10;
public:
    void output(int x,int y){
        //this仍就是值捕获,可读不可写,但是this指向的内容可读可写,成员变量可读可写
        auto x1=[this]{return num;};
        auto x2=[this]{return num++;};
        //this只捕获的成员变量,要是捕获其他的要自己加内容
        auto x3=[this,x,y]{return num+x+y;};
    }
};
int main(){

    return 0;
}
#include <iostream>
#include <vector>

using namespace std;

int main(){
    vector<int> vec={1,2,3,4,5,6};
    auto f1=[&]()->vector<int>{return {1,2};};
    return 0;
}
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main(){
    vector<int> vec={1,2,3,4,5,6};
    sort(vec.begin(),vec.end(),[](int a,int b){return a>b;});
    for(auto it:vec){
        cout<<it<<"  ";
    }
    return 0;
}

shared_ptr(共享型智能指针)

初始化

#include <iostream>
#include <memory>


using namespace std;

int main(){
    //指针指针的初始化:
    shared_ptr<int> ptr1(new int(420));
    cout<<"ptr1的引用计数"<<ptr1.use_count()<<endl;
    shared_ptr<char> ptr2(new char[40]);
    cout<<"ptr2的引用计数"<<ptr2.use_count()<<endl;
    shared_ptr<int> ptr3;
    cout<<"ptr3的引用计数"<<ptr3.use_count()<<endl;
    shared_ptr<int> ptr4(nullptr);
    cout<<"ptr4的引用计数"<<ptr4.use_count()<<endl;
    int *p=new int(50);
    //<注意>:不要使用一个原始指针去初始化不同的智能指针,会造成同一块内存多次释放
    shared_ptr<int> p1(p),p2(p);
    cout<<"p1的引用计数"<<p1.use_count()<<endl;
    cout<<"p2的引用计数"<<p2.use_count()<<endl;
    /*
     * 用智能指针初始化一个不同的智能指针计数会累计++
     * 但是如果用原始指针去初始化不同的智能指针计数不会累加,这让会造成同一块内存被释放多次的错误
     * */
    return 0;
}

拷贝构造和移动构造初始化指针指针

#include <iostream>
#include <memory>
using namespace std;

int main(){
    shared_ptr<int> ptr1(new int(5));
    //拷贝构造
    shared_ptr<int> ptr2(ptr1);
    shared_ptr<int> ptr3(ptr1);
    //移动构造
    shared_ptr<int> ptr4(move(ptr1));
    shared_ptr<int> ptr5(move(ptr2));
    //因为移动构造是把自己的东西给别人,自己就没了
    // 而拷贝构造是复制了一份给别人
    cout<<"ptr1的引用计数为:"<<ptr1.use_count()<<endl;
    cout<<"ptr2的引用计数为:"<<ptr2.use_count()<<endl;
    cout<<"ptr3的引用计数为:"<<ptr3.use_count()<<endl;
    cout<<"ptr4的引用计数为:"<<ptr4.use_count()<<endl;
    cout<<"ptr5的引用计数为:"<<ptr5.use_count()<<endl;
    return 0;
}

make_shared函数初始化指针指针

#include <iostream>
#include <memory>
using namespace std;
class Test{
public:
    Test(){
        cout<<"无参构造"<<endl;
    }
    Test(int x){
        cout<<"int类型构造函数"<<endl;
    }
    Test(string str){
        cout<<"string类型的构造函数"<<endl;
    }
    ~Test(){
        cout<<"析构函数"<<endl;
    }
};
int main(){
    shared_ptr<Test> ptr1= make_shared<Test>(50);
    shared_ptr<Test> ptr2= make_shared<Test>("string");
    shared_ptr<Test> ptr3= make_shared<Test>();
    return 0;
}

reset()

#include <iostream>
#include <memory>
using namespace std;

int main(){
    shared_ptr<int> ptr1= make_shared<int>(50);
    shared_ptr<int> ptr2=ptr1;
    shared_ptr<int> ptr3=ptr1;
    shared_ptr<int> ptr4=ptr1;
    cout<<"ptr1的引用计数为:"<<ptr1.use_count()<<endl;
    cout<<"ptr2的引用计数为:"<<ptr2.use_count()<<endl;
    cout<<"ptr3的引用计数为:"<<ptr3.use_count()<<endl;
    cout<<"ptr4的引用计数为:"<<ptr4.use_count()<<endl;
    ptr4.reset(new int(4));
    cout<<"ptr1的引用计数为:"<<ptr1.use_count()<<endl;
    cout<<"ptr2的引用计数为:"<<ptr2.use_count()<<endl;
    cout<<"ptr3的引用计数为:"<<ptr3.use_count()<<endl;
    cout<<"ptr4的引用计数为:"<<ptr4.use_count()<<endl;
    return 0;
}

unique_ptr(独占型智能指针)

#include <iostream>
#include <memory>

using namespace std;
class A{
    int a;
public:
//    A()=delete;
};

int main(){
    A a;//借助delete关键字把A的构造函数删除了
    unique_ptr<A> ptr1(new A());
//    unique_ptr<A> ptr2(ptr1);//拷贝构造函数被删除了,借助的是delete关键字
    unique_ptr<A> ptr3(move(ptr1));//移动构造是把ptr1的资源给ptr3了,不是ptr3和ptr1指向同一块内存
    /*
     * unique_ptr的拷贝构造和复制运算符都被delete了,但是可以使用移动构造
     * */
    unique_ptr<int> ptr4(new int (4));
    int *p=ptr4.get();
    cout<<*p<<endl;
    ptr4.reset(new int(3));
    cout<<*ptr4<<endl;
    return 0;
}
#include <iostream>
#include <memory>

using namespace std;

unique_ptr<int> fun(){
    unique_ptr<int> a(new int(10));
    return a;
}

int main(){
    //走的移动构造,返回值为右值
    unique_ptr<int> b=fun();
    cout<<*b<<endl;
    return 0;
}

weak_ptr

#include <iostream>
#include <memory>

using namespace std;
/*
 * weak_ptr主要为了解决 shared_ptr循环引用问题
 * weak_ptr不处理原始指针,没* ->,不能操作资源,也没有引用计数
 * 它的只要功能是作为一个旁观者检测shared_ptr的资源是否存在
 * */

int main(){
    shared_ptr<int> sp1(new int(5));
    shared_ptr<int> sp2;
    weak_ptr<int> wp1(sp1);
    weak_ptr<int> wp2(sp2);
    cout<<"wp1:"<<wp1.use_count()<<endl;
    cout<<"wp2:"<<wp2.use_count()<<endl;
    return 0;
}
#include <iostream>
#include <memory>

using namespace std;
/*
 * weak_ptr主要为了解决 shared_ptr循环引用问题
 * weak_ptr不处理原始指针,没* ->,不能操作资源,也没有引用计数
 * 它的只要功能是作为一个旁观者检测shared_ptr的资源是否存在
 * */

int main(){
    shared_ptr<int> sp(new int(4));
    weak_ptr<int>wp(sp);
    cout<<wp.expired()<<endl;
    //expired()函数可以检测weak_ptr检测的shared_ptr是否被释放,0表示没释放,1表示释放了
    wp.reset();
    cout<<wp.expired()<<endl;
    return 0;
}
#include <iostream>
#include <memory>

using namespace std;
/*
 * weak_ptr主要为了解决 shared_ptr循环引用问题
 * weak_ptr不处理原始指针,没* ->,不能操作资源,也没有引用计数
 * 它的只要功能是作为一个旁观者检测shared_ptr的资源是否存在
 * */

int main(){
    shared_ptr<int> sp(new int(4)),sp1;
    weak_ptr<int>wp(sp);
    sp1=wp.lock();
    cout<<sp1.use_count()<<endl;
    return 0;
}

shared_ptr的实现

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

#include <iostream>


using namespace std;
class A{
public:
    A()=default;//default表示这个函数以默认的形式存在
    A(const A& other){
        cout<<"拷贝构造"<<endl;
    }
};

int main(){
    A a;//有拷贝构造就没有默认的构造了

    return 0;
}
#pragma once
#include <iostream>

using namespace std;

template <typename T>

class Ref{
    T* obj= nullptr;
    int r_count=0;
public:
    inline void reduce(){
        r_count--;
        if(!r_count) {
            delete obj;
        }
    }
    inline void increase(){
        r_count++;
    }
};
template <typename F>

class Myshare_ptr{
    Ref<F> *r= nullptr;
public:
    Myshare_ptr()=default;
    ~Myshare_ptr(){
        if(r) r->reduce();
    }
    Myshare_ptr(F* newP){
        cout<<"-----------调用构造函数------------"<<endl;
        r=new Ref<F>(newP);
    }
    Myshare_ptr(const Myshare_ptr& other){
        cout<<"-----------调用拷贝构造函数------------"<<endl;
        this->r=other.r;
        if(r) r->increase();
    }
    Myshare_ptr(Myshare_ptr&& other){
        cout<<"-----------调用移动构造函数------------"<<endl;
        this->r=other.r;
        other.r=nullptr;
    }
    Myshare_ptr operator=(const Myshare_ptr& other){
        cout<<"-----------调用赋值运算符------------"<<endl;

    }
};

  • 9
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值