网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
test();
system("pause");
return 0;
}
#### 1.3.4const修饰成员函数
**常函数:**
* 成员函数后加const后我们称为这个函数为常函数 (void change\_2() const)
* 常函数内不可修改成员属性
* 成员函数声明时加关键字mutable后,在常函数中依然可以修改(mutable int b;)
**常对象:**
* 声明对象前加const称该对象为常对象(const A q;)
* 常对象只能调用常函数
#include
using namespace std;
class A
{
public:
void change_1()
{
a = 100;
b = 100;
}
//在成员函数后面加const,修饰的是this指针,让指针指向的值也不可以修改
void change_2() const //相当于 const A* const this
{
//a = 100; //不可以修改
b = 100; //特殊变量,可以修改
}
int a;
mutable int b; //特殊变量,即使在常函数中也可以修改
};
void test()
{
A p;
p.a = 100;
p.b = 100;
p.change_1();
p.change_2();
}
void test2()
{
const A q;
//q.a = 100; //常对象不能修改普通值
q.b = 100;
//q.change_1(); //错误,常对象只能调用常函数,因为普通成员函数可以修改属性
q.change_2();
}
int main()
{
test();
system(“pause”);
return 0;
}
### 1.4友元函数
友元的关键字:**friend**
三种实现形式:
* 全局函数做友元
即:当全局函数中的对象想要访问类中的私有属性时,需要将该全区函数在类中加以声明为friend类型
>
> class Student
> {
> //此时全局函数show是类Student的好朋友,可以访问类的私有成员
> friend void show(Student &a); //此时全局函数中的对象都可以访问类中的私有属性
> ........
>
>
>
#include
using namespace std;
#include
class Student
{
friend void show(Student &a);
public:
Student()
{
age = 18;
name = “小明”;
}
string name;
private:
int age;
};
void show(Student &a)
{
cout << a.name << endl;
cout << a.age << endl;
}
int main()
{
Student m;
show(m);
system(“pause”);
return 0;
}
* 类做友元
>
> class Class
> {
> ..........
> };
>
>
> class Student
> {
> friend class Class;
> .........
>
>
>
#include
using namespace std;
#include
class Student;
class Class
{
public:
Class();
void show();
private:
Student *s;
};
class Student
{
friend class Class;
public:
Student()
{
age = 18;
name = “小明”;
}
string name;
private:
int age;
};
Class::Class()
{
s = new Student;
}
void Class::show()
{
cout << s->age << endl;
cout << s->name << endl;
}
int main()
{
Class a;
a.show();
system(“pause”);
return 0;
* 成员函数做友元
>
> class Class
> {
> ..........
> };
>
>
> class Student
> {
> friend void Class::show(); //和类做友元类似,此处把类变成了函数
> .........
>
>
>
#include
using namespace std;
#include
class Student;
class Class
{
public:
Class();
void show();
void show1();
private:
Student *s;
};
class Student
{
friend void Class::show();
public:
Student()
{
age = 18;
name = “小明”;
}
string name;
private:
int age;
};
Class::Class()
{
s = new Student;
}
void Class::show()
{
cout << s->age << endl; //类的友元函数,可以访问私有属性
cout << s->name << endl;
}
void Class::show1()
{
// cout << s->age << endl; //不能访问私有属性
cout << s->name << endl;
}
int main()
{
Class a;
a.show();
a.show1();
system(“pause”);
return 0;
}
### 1.5运算符重载
概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
#### 1.5.1加号运算符重载
实现两个自定义数据类型相加的运算
和正常的相加思路类似,只不过这里需要引用其他的对象作为参数;
>
> //成员函数实现“+”运算符重载 p1=p2+p3;类似于:p1=p2.operator+(p3);
> Class operator+(const Class &p){
> Class q(0, 0);
> q.a = this->a + p.a;
> q.b = this->b + p.b;
> return q;
> }
>
>
>
>
> //全局函数实现“+”运算符重载 p1=p2+p3;类似于:p1=operator+(p2,p3);
> Class operator+(const Class &p1, const Class &p2){
> Class q(0,0);
> q.a = p1.a + p2.a;
> q.b = p1.b + p2.b;
> return q;
> }
>
>
>
#include
using namespace std;
#include
class Class
{
public:
Class(int a, int b) :a(a), b(b){}
void show()
{
cout<<“a=”<< a << " b=" << b << endl;
}
//成员函数实现“+”运算符重载
Class operator+(const Class &p){
Class q(0, 0);
q.a = this->a + p.a;
q.b = this->b + p.b;
return q;
}
//private:
int a;
int b;
};
全局函数实现“+”运算符重载
//Class operator+(const Class &p1, const Class &p2){
// Class q(0,0);
// q.a = p1.a + p2.a;
// q.b = p1.b + p2.b;
// return q;
//}
int main()
{
Class a(10,10);
Class b(20, 30);
Class c(0, 0);
c= a + b;
c.show();
system(“pause”);
return 0;
}
#### 1.5.2运算符重载
作用:可以输出自定义数据类型
>
> //只能用全局函数实现“<<”运算符重载
> ostream & operator<<(ostream &cout, const Class &p) //本质operator<<(cout,p),简化cout<<p
> {
> cout << p.a << " " << p.b << endl;
> return cout; //此处返回cout的为了能够实现链式编程,可以多次返回输出
> }
>
>
>
#include
using namespace std;
#include
class Class
{
public:
Class(int a, int b) :a(a), b(b){}
int a;
int b;
};
//如果利用成员函数重载 左移运算符 p.operator<<(cout) 简化版本 p<<cout;p在左侧错误
//只能用全局函数实现“<<”运算符重载
ostream & operator<<(ostream &cout, const Class &p) //本质operator<<(cout,p),简化cout<<p
{
cout << p.a << " " << p.b << endl;
return cout;
}
int main()
{
Class a(10,10);
cout << a<<endl;
system(“pause”);
return 0;
}
#### 1.5.3递增运算符重载
作用:通过重载递增运算符,实现自己的整型数据
>
> //重载前置++运算符
> Class &operator++() //返回引用是为了一直对一个数据进行增值操作
> {
> a++;
> return \*this;
> }
>
>
> //重载后置++运算符 //此时不能再使用链式操作了
> Class operator++(int) //此处int作为占位符,用来区分前置还是后置
> {
> Class p = \*this; //记录当前本身的值,然后让本身的值加1,但是返回的值还是以前的值
> a++;
> return p;
> }
>
>
> 前置返回的是一个引用,而后置返回的是结果是一个临时对象。因为后置的时候原来的对象已经被++改变了,所以需要一个新对象来保存改变之前的值。而前置用引用则是为了不产生临时变量,减少内存的消耗而已。
>
>
> 所以前置引用可以使用链式的方法,多次前置++,而后置不可以多次++;
>
>
>
#include
using namespace std;
#include
class Class
{
friend ostream & operator<<(ostream &cout, const Class &p);
public:
Class(int a) :a(a){}
//重载前置++运算符
Class &operator++() //返回引用是为了一直对一个数据进行增值操作
{
a++;
return *this;
}
//重载后置++运算符
Class operator++(int) //此处int作为占位符,用来区分前置还是后置
{
Class p = *this; //记录当前本身的值,然后让本身的值加1,但是返回的值还是以前的值
a++;
return p;
}
private:
int a;
};
ostream & operator<<(ostream &cout, const Class &p)
{
cout << p.a << endl;
return cout;
}
int main()
{
Class a(10);
cout << ++(++a)<<endl;
cout << a << endl;
cout << a++ << endl;
cout << a << endl;
system(“pause”);
return 0;
}
#### 1.5.4赋值运算符重载
赋值运算符需要注意的地方就是当数据在堆区存储时,注意开辟新的空间以及及时释放空间
>
> void operator = (Class &p)
> {
> if (a != NULL) //如果已经有值,需要先释放,再进行赋值
> {
> delete a;
> a = NULL;
> }
> //深拷贝
> a = new int(\*p.a);
> }
>
>
>
#include
using namespace std;
#include
class Class
{
friend ostream & operator<<(ostream &cout, const Class &p);
public:
Class(int a){
this->a = new int(a);
}
void operator = (Class &p)
{
if (a != NULL)
{
delete a;
a = NULL;
}
//深拷贝
a = new int(*p.a);
}
private:
int *a;
};
ostream & operator<<(ostream &cout, const Class &p)
{
cout << *p.a << endl;
return cout;
}
int main()
{
Class a(10);
Class b(20);
a = b;
cout << a << endl;
system(“pause”);
return 0;
}
#### 1.5.5关系运算符重载
作用:可以让两个自定义类型对象进行对比操作
>
> bool operator == (Class &p) //相对比较简单,就简单传值,然后对比一下
> {
> if (this->a == a) {
> return true;
> }
> else {
> return false;
> } }
>
>
>
#include
using namespace std;
class Class
{
public:
Class(int a){
this->a = a;
}
bool operator == (Class &p)
{
if (this->a == a)
{
return true;
}
else
{
return false;
}
}
private:
int a;
};
int main()
{
Class a(10);
Class b(20);
if (a == b)
{
cout << “两个数相等” << endl;
}
else
{
cout << “两个数不相等” << endl;
}
system(“pause”);
return 0;
}
#### 1.5.6函数调用运算符重载
* 函数调用运算符()也可以重载
* 由于重载后使用的方法非常像函数的调用,因此称为仿函数
* 仿函数没有固定的写法,非常灵活
>
> void operator()(string world)
> {
> cout << world << endl;
> }
>
>
>
#include
using namespace std;
#include
class Class
{
public:
void operator()(string world)
{
cout << world << endl;
}
};
int main()
{
Class a;
a(“hells world”); //由于使用起来类似于函数调用,因此称为仿函数;非常灵活,没有固定写法
//此处为匿名函数对象
Class()("hello world"); //Class()代替了类似于a
system("pause");
return 0;
}
### 1.6继承
继承的好处:减少重复代码
继承语法:class A(子类) : 继承方式 B(父类)
子类也称为派生类;父类也称为基类
**派生类中的成员,包含两大部分:**
* 从基类继承过来的,表现其共性
* 自己增加的函数,体现其个性
#### 1.6.1 继承方式
一共有三种:
* 公有继承 (三种权限不变)
* 保护继承 (公+保->保,私不变)
* 私有继承 (公+保->私,私不变)
* 继承时,私有成员子类均不可访问,保护成员子类可以访问
下面这段代码,详细注释了各种情况下的访问和继承
#include
using namespace std;
#include
class Class
{
public:
int a;
protected:
int b;
private:
int c;
};
class sun1:public Class
{
void fun()
{
a = 10; //公有 不变
b = 10; //保护 不变
//c = 10; 错误,子类不可访问父类的私有成员
}
};
class sun2 :protected Class
{
void fun()
{
a = 10; //保护 变为
b = 10; //保护 不变
//c = 10; 错误,子类不可访问父类的私有成员
}
};
class sun3 :private Class
{
void fun()
{
a = 10; //私有 变为
b = 10; //私有 变为
//c = 10; 错误,子类不可访问父类的私有成员
}
};
void test()
{
sun1 p1;
p1.a = 10;
//p1.b = 20; //保护b不可访问
//p1.c = 30; //私有c不可访问
sun2 p2;
//p2.a = 10; //保护a不可访问
//p2.b = 20; //保护b不可访问
//p2.c = 30; //私有c不可访问
sun3 p3;
//p3.a = 10; //私有a不可访问
//p3.b = 20; //私有b不可访问
//p3.c = 30; //私有c不可访问
}
int main()
{
test();
system(“pause”);
return 0;
}
#### 1.6.2继承中的对象模型
继承规则:
* 父类中所有非静态成员属性都会被子类继承下去
* 父类中私有成员属性,是被编译器给隐藏了,因此是访问不到的,但是确实是继承下去了
>
> class Class{
> public: int a;
> protected: int b;
> private: int c;
> };
> class sun1 :public Class{
> int a;
> }; //此时sun1定义出的对象大小就为16,继承的三个加上新增加的一个
>
>
>
#### 1.6.3继承中构造和析构顺序
先构造父类,再构造子类,析构的顺序与构造的顺序相反(可以自己写代码实验)
#### 1.6.4继承同名成员处理方式
* 访问子类同名成员,直接访问即可
* 访问父类同名成员,需要加作用域
>
> sun p;
> cout << p.a << endl;
> cout << p.Base::a << endl;
>
>
>
* 当子类与父类拥有同名成员函数,子类会隐藏父类中所有版本的同名成员函数
* 如果想访问父类中隐藏的同名成员函数,需要加父类的作用域
>
> p.change(); //子类中的成员函数
> //p.change(a); //此处虽然是重载函数,但是只要子类父类发生重名,子类就会隐藏父类所有同名函数
> p.Base::change(9); //父类中的带参成员函数
> p.Base::change(); //父类中的不带参成员函数
>
>
>
#include
using namespace std;
#include
class Base{
public:
Base()
{
a = 10;
}
void change()
{
a = 100;
}
void change(int x)
{
a = x;
}
int a;
};
class sun:public Base{
public:
sun()
{
a = 20;
}
void change()
{
a = 200;
}
int a;
};
void test()
{
sun p;
//成员
cout << p.a << endl;
cout << p.Base::a << endl;
//成员函数
p.change(); //子类中的成员函数
//p.change(a); //此处虽然是重载函数,但是只要子类父类发生重名,子类就会隐藏父类所有同名函数
p.Base::change(9); //父类中的带参成员函数
p.Base::change(); //父类中的不带参成员函数
}
int main()
{
test();
system(“pause”);
return 0;
}
#### 1.6.5继承同名静态成员处理方式
静态成员和非静态成员出现同名,处理方式一致,只不过有两种访问方式
* 访问子类同名成员,直接访问即可
* 访问父类同名成员,需要加作用域
>
> cout << p.a << endl;
> cout << p.Base::a << endl;
>
>
> cout << sun::a << endl;
> cout << sun::Base::a << endl;
>
>
>
#### 1.6.6多继承语法
C++中允许**一个类继承多个类**
语法: class 子类 :继承方式 父类1,继承方式 父类2........
(实际开发中不建议)
>
> class Base :public Base1, public Base2{}
>
>
>
>
> **当父类中出现同名成员,需要加作用域进行区分**
>
>
> cout << p.a << endl;
> cout << p.Base1::a << endl;
> cout << p.Base2::a << endl;
>
>
>
#include
using namespace std;
#include
class Base1{
public:
Base1()
{
a = 10;
}
int a;
};
class Base2{
public:
Base2()
{
a = 20;
}
int a;
};
class Base :public Base1, public Base2
{
public:
Base()
{
a = 30;
}
int a;
};
void test()
{
Base p;
cout << p.a << endl;
cout << p.Base1::a << endl;
cout << p.Base2::a << endl;
}
int main()
{
test();
system(“pause”);
return 0;
}
#### 1.6.7菱形继承
概念:
1. 两个派生类继承同一个基类
2. 又有某个类同时继承两个派生类
3. 这种继承称为菱形继承

如图所示:B1,B2继承A,C又继承B1,B2。
当出现这种零星继承时,如果两个父类拥有相同数据,需要加以作用域区分
这份数据我们知道,只需要一份就足够了,而菱形继承导致数据有两份,资源浪费。
**解决方法:**
* 利用虚继承 解决菱形继承的问题
* 继承之前加上关键字virtual变为虚继承
* A变为虚基类
>
> class B1 :virtual public A{};
> class B2 :virtual public A{};
>
>
>
### 1.7多态
#### 1.7.1多态的基本概念
多态分为两类:
* 静态多态:函数重载和运算符重载属于静态多态,复用函数名
* 动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态的区别:
* 静态多态的函数地址早绑定-编译阶段确定函数地址
* 动态多态的函数地址晚绑定-运行阶段确定函数地址
地址早绑定的话,在编译阶段就确定了函数地址,因此函数就不会更改了,一直显示某一个
若要按要求执行,使得函数不提前绑定,就需要用到动态多态,即加上关键字"virtual",变成虚函数
动态多态满足的条件:
1. 有继承关系
2. 子类重写父类的虚函数 (重写:函数返回值类型,函数名,参数列表完全一致称为重写)
动态多态的使用:
1. 父类指针或者引用,执行子类对象
#include
using namespace std;
#include
class Base1{
public:
//虚函数
virtual void show()
{
cout << “Base1” << endl;
}
};
class Base2:public Base1
{
public:
void show()
{
cout << “Base2” << endl;
}
};
class Base3 :public Base1
{
public:
void show()
{
cout << “Base3” << endl;
}
};
void test(Base1 &p)
{
p.show();
}
int main()
{
Base1 p1;
Base2 p2;
Base3 p3;
test(p1);
test(p2);
test(p3);
system(“pause”);
return 0;
}
#### 1.7.2 纯虚函数和抽象类
因为在多态中,通常父类的虚函数实现毫无意义,通常都是调用子类重写的内容,因此可以将虚函数改为纯虚函数
语法:virtual 返回值类型 函数名(参数列表)=0
当类中有了纯虚函数,这个类也称为抽象类
**抽象类特点:**
* 无法实例化对象
* 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
>
> class Base1{
> public:
> //纯虚函数,所以此类也被称为抽象类
> virtual void show() = 0;
> };
>
>
> 则不能Base1 p; 因为Base1是抽象类,抽象类不能实例化对象。
>
>
>
#include
using namespace std;
#include
class Base1{
public:
//纯虚函数,所以此类也被称为抽象类
virtual void show() = 0;
};
class Base2:public Base1
{
public:
//重写父类Base1中的show函数
void show(){ cout << “Base2”; }
};
class Base3 :public Base1
{
};
void test()
{
//Base1 p; //抽象类不能实例化对象
//Base3 p; //如果不重写抽象类中的纯虚函数,则子类也变成抽象类
Base2 p;
p.show();
}
int main()
{
test();
system(“pause”);
return 0;
}
#### 1.7.3虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方法:将父类中的析构函数改为**虚析类**或者**纯虚析构**
* 如果子类中没有堆区数据,可以不写虚析构或者纯虚函数
虚析类和纯虚析构共性:
* 可以解决父类指针释放子类对象
* 都需要有具体的函数实现
虚析类和纯虚析构区别:
* 如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名()=0
>
> //利用虚析构可以解决父类释放子类对象时堆区数据泄露的问题
> virtual ~Base1()
> {
> cout << "Base1的析构函数" << endl;
> }
>
>
> //纯虚析构 需要声明也需要实现
> //有了纯虚析构,这个类也属于抽象类,无法实例化对象
> virtual ~Base1() = 0;
>
>
>
#include
using namespace std;
#include
class Base1{
public:
Base1()
{
cout << “Base1的构造函数” << endl;
}
利用虚析构可以解决父类释放子类对象时堆区数据泄露的问题
//virtual ~Base1()
//{
// cout << "Base1的析构函数" << endl;
//}
//纯虚析构 需要声明也需要实现
//有了纯虚析构,这个类也属于抽象类,无法实例化对象
virtual ~Base1() = 0;
};
Base1::~Base1()
{
cout << “Base1纯虚析构函数” << endl;
}
class Base2:public Base1
{
public:
Base2()
{
cout << “Base2的构造函数” << endl;
}
~Base2()
{
cout << “Base2的析构函数” << endl;
}
};
void test()
{
Base1 *p = new Base2;
//父类指针在析构时候,不会调用子类中析构函数,导致子类如果有堆区数据,会出现内存泄露
delete p;
}
int main()
{
test();
system(“pause”);
return 0;
}
## 2,文件操作
程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放
通过文件可以将数据持久化,需要包含的头文件<fstream>
文件类型分为两种:
* 文本文件:文件以文本的ASCLL码形式存储在计算机中
* 二进制文件:文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂他们
操作文件的三大类
* ofstream:写操作
* ifstream:读操作
* fstream:读写操作
### 2.1文本文件
#### 2.1.1写文件
**步骤:**
>
> 1. 包含头文件:#include<fstream>
> 2. 创建流对象:ofstream test;
> 3. 打开文件:test.open("文件路径",打开方式);
> 4. 写数据:test<<"写入的数据";
> 5. 关闭文件:test.close();
>
>
>
>
> //头文件
> ofstream test;
> test.open("test.txt", ios::out);
> test << "姓名:小明" << endl;
> test.close();
>
>
>
**文件打开方式**
| | |
| --- | --- |
| 打开方式 | 解释 |
| ios::in | 为读文件而打开文件 |
| ios::out | 为写文件而打开文件 |
| ios::ate | 初始位置:文件尾 |
| ios::app | 追加方式写文件 |
| ios::trunc | 如果文件存在先删除,再创建 |
| ios::binary | 二进制方式 |
文件打开方式可以配合使用,利用” | ”操作符。
#### 2.1.2读文件
读文件与写文件步骤相似,但是读取方式相对较多
读取文件步骤:
>
> 1. 包含头文件:#include<fstream>
> 2. 创建流对象:ifstream test;
> 3. 打开文件:test.open("文件路径",打开方式);
> 4. 读数据:四种方式读取
> 5. 关闭文件:test.close();
>
>
>


**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.youkuaiyun.com/topics/618668825)**
}
int main()
{
test();
system("pause");
return 0;
}
2,文件操作
程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放
通过文件可以将数据持久化,需要包含的头文件
文件类型分为两种:
- 文本文件:文件以文本的ASCLL码形式存储在计算机中
- 二进制文件:文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂他们
操作文件的三大类
- ofstream:写操作
- ifstream:读操作
- fstream:读写操作
2.1文本文件
2.1.1写文件
步骤:
- 包含头文件:#include
- 创建流对象:ofstream test;
- 打开文件:test.open(“文件路径”,打开方式);
- 写数据:test<<“写入的数据”;
- 关闭文件:test.close();
//头文件
ofstream test;
test.open(“test.txt”, ios::out);
test << “姓名:小明” << endl;
test.close();
文件打开方式
打开方式 | 解释 |
ios::in | 为读文件而打开文件 |
ios::out | 为写文件而打开文件 |
ios::ate | 初始位置:文件尾 |
ios::app | 追加方式写文件 |
ios::trunc | 如果文件存在先删除,再创建 |
ios::binary | 二进制方式 |
文件打开方式可以配合使用,利用” | ”操作符。
2.1.2读文件
读文件与写文件步骤相似,但是读取方式相对较多
读取文件步骤:
- 包含头文件:#include
- 创建流对象:ifstream test;
- 打开文件:test.open(“文件路径”,打开方式);
- 读数据:四种方式读取
- 关闭文件:test.close();
[外链图片转存中…(img-jyrugdrU-1715813482045)]
[外链图片转存中…(img-9KjTS9NV-1715813482045)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新