文章目录
下面继续还是c++中类和对象的内容,继续补充,只不过前面的比较简单,下面是一些较深层的东西。
1、构造函数和析构函数
1、认识构造函数
c++面向对象是来源于生活的设计,每个对象在生成的时候也会有初始化设置,当不需要对象将其删除的话也需要对数据进行清理的操作,c++中使用构造函数和析构函数来解决上面的问题,这两个函数会被编译器自动调用,完成对象的初始化和清理的工作,这个部门是编译器要求的,如果没有做的话,编译器也会做,不过那样就是一个空实现。
下面来说明下这两个函数:
- 构造函数:作用在创建对象的时候为对象的成员属性赋值,由编译器自动调用,无需手动调用。
- 析构函数:对象销毁前系统自动调用,执行清理工作。
下面的例子展示了构造函数和析构函数的用法
如果要展示析构函数,需要我们创建一个被释放的函数,如下所示:
总结一下,这两种函数的写法就是:
构造函数语法:类名(){}
- 构造函数,没有返回值也不写void
- 函数名称与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
析构函数语法: ~类名(){}
- 析构函数,没有返回值也不写void
- 函数名称与类名相同,在名称前加上符号 ~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
2、构造函数的分类
根据上面的总结,可以将构造函数进行分类,分类如下所示:
- 按参数分为: 有参构造和无参构造
- 按类型分为: 普通构造和拷贝构造
下面是几种分类的写法
下面进行函数的调用,如下所示(注意几种不同的方式调用的结果):
运行结果如下所示:
3、拷贝构造函数的时机
通常有三种情况下会使用拷贝构造函数,如下所示:
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
具体的调用情况如下所示,这是还是用上面的例子:
三个方面的体现如下所示:
说到拷贝就还有一个比较重要的概念吧,就是深拷贝和浅拷贝
这里一般可以理解为:
- 浅拷贝:简单的赋值拷贝操作
- 深拷贝:在堆区重新申请空间,进行拷贝操作
比如上面的例子中要实现深拷贝和浅拷贝:
2、类内元素的处理
1、初始化列表
c++中可以使用列表来初始化属性,如下所示:
2、类的嵌套(类作为另一个类的成员)
这种就是普通的嵌套了,可以体现为:
class A {}
class B
{
A a;
}
但是这样的情况下要注意一下构造函数和析构函数的顺序问题,下面一个例子来说明:
运行结果如下所示:
由此可知,顺序应该是构造函数嵌套的类先执行,析构函数则相反。
3、c++对象模型
1、占空间的大小
在c++中,类内成员变量和成员函数是分开储存的,只有非静态成员变量才属于类的对象上面,如下所示:
所以运行起来就是下面的情况:
2、this指针
前面的例子说明每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码,那么实际情况下这么多个对象之间如何进行区分,这就this指针的问题了。
this指针的含义如下:
- this指针是隐含每一个非静态成员函数内的一种指针
- this指针不需要定义,直接使用即可
一般我们需要使用this指针的用途如下:
- 当形参和成员变量同名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用return *this
具体使用如下所示:
3、友元
在程序里面有些私有属性,也希望类外特殊的一些函数或者类可以进行访问,就需要友元,通过使用友元就可以让一个函数或者类去访问另一个类中的私有成员,一般我们使用友元关键字为friend。
友元的三种常见情况为:
- 全局函数做友元
- 类做友元
- 成员函数做友元
下面来看下三种友元的实现:
全局函数做友元
类做友元
下面是调用里面的成员:
成员函数做友元
4、继承
1、继承的基本实现
前面的内容都是在讲类的封装的东西,下面要开始介绍继承了,c++面向对象有三大特性,分别是封装,继承和多态。
继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行效率的效果。当创建一个类时,不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。
基类和派生类的关系如下所示:
继承的特点可以理解为:
下级别的成员除了拥有上一级的共性,还有自己的特性。这个时候我们就可以考虑利用继承的技术,减少重复代码
运行结果如下所示:
2、三种继承方式
继承方式一共有三种,如下所示:
- 公共继承
- 保护继承
- 私有继承
这几种方式也是用类里面的一些关键字来实现的,如下所示:
3、多继承
c++中允许多继承,就是一个子类可以继承多个父类,如下所示:
下面对上面的两个类进行继承
5、多态
1、多态的概念
首先还是:多态是C++面向对象三大特性之一
多态可以分为两类,分别是静态多态和动态多态:
- 静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
- 动态多态: 派生类和虚函数实现运行时多态
他们的区别在于什么时候确定函数地址,静态多态在编译的时候就确定了函数地址,而动态多态在运行过程中才会确定函数地址。
下面来编写一个例子:
调用如下所示:
运行结果如下所示:
多态满足条件:
- 1、有继承关系
- 2、子类重写父类中的虚函数(重写:函数返回值类型 函数名 参数列表 完全一致称为重写)
多态使用时的条件
父类指针或引用指向子类对象
完整代码如下:
class Animal
{
public:
virtual void speak()//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
{
cout << "动物在说话" << endl;
}
};
class Cat :public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
};
class Dog :public Animal
{
public:
void speak()
{
cout << "小狗在说话" << endl;
}
};
void DoSpeak(Animal & animal)
{
animal.speak();
}
void test01()
{
Cat cat;
DoSpeak(cat);
Dog dog;
DoSpeak(dog);
}
int main() {
test01();
system("pause");
return 0;
}
2、多态应用案例
下面用一个计算器的例子来看多态的实现,代码如下:
class AbstractCalculator
{
public:
virtual int getResult()
{
return 0;
}
int m_Num1;
int m_Num2;
};
//加法计算器
class AddCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 + m_Num2;
}
};
//减法计算器
class SubCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 - m_Num2;
}
};
//乘法计算器
class MulCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 * m_Num2;
}
};
void test02()
{
//创建加法计算器
AbstractCalculator *abc = new AddCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc; //用完了记得销毁
//创建减法计算器
abc = new SubCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc;
//创建乘法计算器
abc = new MulCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc;
}
int main()
{
test02();
system("pause");
return 0;
}
6、文件操作
首先来追究一下来源,我们平时运行程序,或者是跑单片机代码都是一样的,都是程序在RAM上跑,跑完就消失了,我们在计算机上也是一样的,程序结束了,那些变量也就都没了,然后就又是重新初始化这样的,这是不合理的,因此就有了文件操作(单片机中就是读写flash了)。
1、基本函数
程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放,通过文件可以将数据持久化,C++中对文件操作需要包含头文件 < fstream >
常见的文件类型分为两种:
- 文本文件: 文件以文本的ASCII码形式存储在计算机中
- 二进制文件: 文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们
操作文件的三大类:
- ofstream:写操作
- ifstream: 读操作
- fstream : 读写操作
2、写文件操作示例
下面主要是对文本文件进行操作,如下所示:
写文件步骤如下:
- 包含头文件
#include <fstream> - 创建流对象
ofstream ofs; - 打开文件
ofs.open(“文件路径”,打开方式); - 写数据
ofs << “写入的数据”; - 关闭文件
ofs.close();
其中文件的打开有如下几种方式:
打开方式 | 含义 |
---|---|
ios::in | 为读文件而打开文件 |
ios::out | 为写文件而打开文件 |
ios::ate | 初始位置:文件尾 |
ios::app | 追加方式写文件 |
ios::trunc | 如果文件存在先删除,再创建 |
ios::binary | 二进制方式 |
下面是写文件过程:
可以看到写的文件如下:
写入的内容:
代码如下:
#include<iostream>
using namespace std;
#include<string>
#include <fstream>
void test01()
{
ofstream wr_f;
wr_f.open("test.txt", ios::out);
wr_f << "写文件操作!" << endl;//利用<<可以向文件中写数据
wr_f.close();
}
int main() {
test01();
system("pause");
return 0;
}
3、读文件操作示例
读文件的步骤如下所示:
- 包含头文件
#include <fstream> - 创建流对象
ifstream ifs; - 打开文件并判断文件是否打开成功
ifs.open(“文件路径”,打开方式); - 读数据
四种方式读取 - 关闭文件
ifs.close();
下面是读文件的过程,将读取的内容打印出来
打印结果如下:
代码如下:
#include<iostream>
using namespace std;
#include<string>
#include <fstream>
void test01()
{
ifstream op_f;
op_f.open("test.txt", ios::in);
if (!op_f.is_open())
{
cout << "文件打开失败" << endl;
return;
}
char c;
while ((c = op_f.get()) != EOF)
{
cout << c;
}
op_f.close();
}
int main() {
test01();
system("pause");
return 0;
}