C++笔记

发布时间:2024年01月13日

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 但是别给自己找麻烦还是别这么用了!

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;
}

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;

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;
}
文章来源:https://blog.csdn.net/m0_75178021/article/details/135573319
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。