根据B站学习:
1、new和delete
C++中利用new操作符在堆区开辟数据
堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符 delete
语法: new 数据类型
利用new创建的数据,会返回该数据对应的类型的指针
int* func1()
{
int* a = new int(10);
return a;
}
void func2()
{
int* arr = new int[10];
for (int i = 0; i < 10; i++)
{
arr[i] = i + 100;
}
for (int i = 0; i < 10; i++)
{
cout << arr[i] << endl;
}
delete[] arr;
}
int main()
{
int* p = func1();
cout << *p << endl;
delete p;
func2();
system("pause");
return 0;
}
2、引用
2.1、引用的基本使用
作用:给变量起一个别名
语法:数据类型 &别名 = 原名
注意事项:
- 引用必须初始化
- 引用在初始化后,不可以改变
#include <iostream>
using namespace std;
//作用: 给变量起别名
//语法: 数据类型& 别名 = 原名
//注意:
//引用必须初始化
//引用在初始化后,不可以改变
void quote(void)
{
cout << "当前区域是“引用”部分-----start" << endl;
int a = 100;
int& b = a;
b = 10000;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
}
2.2、引用做函数参数
作用:函数传参时,可以利用引用的技术让形参修饰实参
优点:可以简化指针修改实参
小结:通过引用参数产生的效果同按地址传递是一样的。引用的语法更清楚简单
2.2.1、引用也可以作为函数返回值
作用:引用是可以作为函数的返回值存在的
注意:不要返回局部变量引用
用法:函数调用作为左值
2.2.2、引用的本质
本质:引用的本质在c++内部实现是一个指针常量.
结论:C++推荐用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了
栗子:
#include <iostream>
using namespace std;
/*值传递*/
void swap01(int a, int b)
{
int temp;
temp = a;
a = b;
b = temp;
}
/*地址传递*/
void swap02(int *a, int *b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
//引用传递
void swap03(int &a, int &b)
{
int temp;
temp = a;
a = b;
b = temp;
}
/*通过引用参数产生的效果同按地址传递是一样的、引用的语法更清楚简单*/
void quote_func(void)
{
int a = 50;
int b = 89;
swap01(a, b);
cout << "引用作为函数参数---值传递 " << "a = " << a << endl;
cout << "引用作为函数参数---值传递 " << "b = " << b << endl;
swap02(&a, &b);
cout << "引用作为函数参数---地址传递 " << "a = " << a << endl;
cout << "引用作为函数参数---地址传递 " << "b = " << b << endl;
swap03(a, b);
cout << "引用作为函数参数---引用传递 " << "a = " << a << endl;
cout << "引用作为函数参数---引用传递 " << "b = " << b << endl;
}
//引用作为函数返回值
int& test01()
{
int a = 100;//局部变量--注意不要返回局部变量
return a;
}
int& test02()
{
static int a = 45;
return a;
}
void quote_return(void)
{
int& a = test01();
cout << "引用作为函数返回值---局部变量" << "a = " << a << endl;
cout << "引用作为函数返回值---局部变量" << "a = " << a << endl;
int &b = test02();
cout << "引用作为函数返回值---静态变量" << "b = " << b << endl;
cout << "引用作为函数返回值---静态变量" << "b = " << b << endl;
test02() = 526;//实际上就是 b = 526
cout << "引用作为函数返回值---静态变量" << "b = " << b << endl;
cout << "引用作为函数返回值---静态变量" << "b = " << b << endl;
}
//C++推荐用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了
//int a = 10;
//自动转换为 int* const ref = &a; 指针常量是指针指向不可改,也说明为什么引用不可更改
//int& ref = a;
//ref = 20; //内部发现ref是引用,自动帮我们转换为: *ref = 20;
2.3 常量引用
作用:常量引用主要用来修饰形参,防止误操作
在函数形参列表中,可以加const修饰形参,防止形参改变实参
栗子:
#include <iostream>
using namespace std;
void constpare(const int& ref)
{
cout << "常量引用--函数参数引用参量" << "ref = " << ref << endl;
}
//常量引用
void quote_const(void)
{
//int& a = 100;//错误的原因是没有进行添加const标识符
const int& a = 100;
//加入const之后就不能再次修改常量的值
//a = 555;
constpare(a);
}
3、函数提高
3.1、函数默认参数
在C++中,函数的形参列表中的形参是可以有默认值的。在使用的过程中,默认值也是可以根据使用函数的过程中传递的参数不同而不同
语法:返回值类型 函数名 (参数= 默认值){}
3.1.1、函数占位参数
C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
语法:返回值类型 函数名 (数据类型){}
/*举例:创建这样的函数*/
void func9(int a, int);
//在引用的时候需要将函数的所有参数都带上:
比如这样func9(10,41);
栗子:
#include <iostream>
using namespace std;
/*
在C++中,函数的形参列表中的形参是可以有默认值的
语法: 返回值类型 函数名 (参数= 默认值){}
*/
//1. 如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值
int func1(int a, int b = 2, int c = 56)
{
return a + b + c;
}
//2. 如果函数声明有默认值,函数实现的时候就不能有默认参数
int func2(int a, int b, int c = 56, int d = 10);
int func2(int a, int b, int c, int d)
{
return a + b + c + d;
}
//函数占位参数
//C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
//语法: 返回值类型 函数名(数据类型) {}
void func3(int a, int)
{
cout << "函数占位功能栗子:" << "占位参数必须填补" << endl;
}
void func_default_para(void)
{
int a = 54;
int b = 10;
//func1和func2函数参数可以补齐、也可以只写变量部分
//func1只写变量部分
cout << "函数默认参数-----" << func1(a) << endl;
//func2全部补全-默认参数部分也是可以进行更改的
//声明是int func2(int a, int b, int c = 56, int d = 10)
//默认参数C和D可以写成50和90
cout << "函数默认参数-----" << func2(a,b,50,90) << endl;
func3(10, 10); //占位参数必须填补
}
3.2、函数重载
作用:函数名可以相同,提高复用性
函数重载满足条件:
- 同一个作用域下
- 函数名称相同
- 函数参数类型不同 或者 个数不同 或者 顺序不同
注意:函数的返回值不可以作为函数重载的条件
小结:函数名相同的情况下,参数的个数、类型、顺序可随意设置,如果有返回值,这些函数的返回值必须一样
3.2.1、引用作为重载时
栗子:
#include <iostream>
using namespace std;
//作用:函数名可以相同,提高复用性
//函数重载满足条件:
//1、同一个作用域下
//2、函数名称相同
//3、函数参数类型不同 或者 个数不同 或者 顺序不同
//注意 : 函数的返回值不可以作为函数重载的条件
//函数重载需要函数都在同一个作用域下
void func()
{
cout << "函数重载---func 的调用!" << endl;
}
void func(int a)
{
cout << "函数重载---func (int a) 的调用!" << endl;
}
void func(double a)
{
cout << "函数重载---func (double a)的调用!" << endl;
}
void func(int a ,double b)
{
cout << "函数重载---func (int a ,double b) 的调用!" << endl;
}
void func(double a, int b)
{
cout << "函数重载---func (double a ,int b)的调用!" << endl;
}
//函数重载时遇到----引用
void func1(int& a)
{
cout << "函数重载---引用---func1 (int &a) 调用 " << endl;
}
void func1(const int& a)
{
cout << "函数重载---引用---func1 (const int &a) 调用 " << endl;
}
//函数重载
void func_overload(void)
{
func();
func(10);
func(3.14);
func(10, 3.14);
func(3.14, 10);
int a = 10;
func1(a); //调用无const
func1(10);//调用有const
}
函数重载时遇到函数默认参数的情况下,应避免使用重载函数
4、类和对象
C++面向对象的三大特征为:封装、继承、多态
C++认为万事万物皆为对象,对象有其属性和行为
例如:
人可以作为对象,属性有姓名、年龄、身高、体重…,行为有走、跑、跳、吃饭、唱歌…
车也可以作为对象,属性有轮胎、方向盘、车灯…,行为有载人、放音乐、放空调…
具有相同性质的对象,我们可以抽象称为类,人属于人类,车属于车类
4.1、封装
封装是C++面向对象三大特性之一
封装的意义:
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
语法:class 类名{ 访问权限: 属性 / 行为 }
4.1.1、三种访问权限
- public 公共权限
- protected 保护权限
- private 私有权限
解释:
- 公共权限 public 类内可以访问 类外可以访问
- 保护权限 protected 类内可以访问 类外不可以访问
- 私有权限 private 类内可以访问 类外不可以访问
4.1.2、struct和class区别
在C++中,struct和class区别就在于默认的访问权限不同
- struct 默认权限为公共
- class 默认权限为私有
4.1.3、将成员属性设置为私有
- 优点1:将所有成员属性设置为私有,可以自己控制读写权限
- 优点2:对于写权限,我们可以检测数据的有效性
栗子:
#include <iostream>
using namespace std;
class student
{
public:
void setname(string name)
{
m_name = name;
}
void setnum(int num)
{
m_num = num;
}
string getname()
{
return m_name;
}
int getnum()
{
return m_num;
}
void showmassage()
{
cout << "封装----姓名:" << m_name << endl;
cout << "封装----学号:" << m_num << endl;
}
public:
string m_name;
int m_num;
};
//三种保护权限
//1、public 公共权限 类内外都可访问
//2、private 私有权限 类内可以访问,类外不可访问
//3、protected 保护权限 类内可以访问,类外不可访问
//成员属性设置为私有
class person
{
public:
//m_name---可读可写
void setname(string name)
{
m_name = name;
}
string getname(void)
{
return m_name;
}
//年龄---只读
int getage(void)
{
return m_age;
}
void setage(int age)
{
if (age < 0 || age > 150)
{
cout << "你个老怪物!" << endl;
return;
}
m_age = age;
}
//情人设置为只写
void setlover(string lover)
{
m_lover = lover;
}
private:
string m_name;
int m_age;
string m_lover;
};
//封装
void capsulation()
{
student stu;
stu.setname("王二毛");
stu.setnum(4545);
stu.showmassage();
person per;
per.setname("王大毛");
cout << "封装--可读可写--姓名:" << per.getname() << endl;
per.setage(12);
cout << "封装--只读--年龄:" << per.getage() << endl;
per.setlover("王大妈");
}
4.1.4、小项目
要求:
设计立方体类(Cube)
求出立方体的面积和体积
分别用全局函数和成员函数判断两个立方体是否相等。
#include <iostream>
using namespace std;
class CUBE
{
public:
void set_l(int l)
{
m_l = l;
}
void set_w(int w)
{
m_w = w;
}
void set_h(int h)
{
m_h = h;
}
int S()
{
return (2 * (m_l * m_w + m_l * m_h + m_w * m_h));
}
int V()
{
return m_l * m_h * m_w;
}
bool equality(CUBE c1)
{
if (c1.S() == S() && c1.V() == V())
return true;
return false;
}
public:
int m_l, m_w, m_h;
};
bool equalityout(CUBE c1, CUBE c2)
{
if (c1.S() == c2.S() && c1.V() == c2.V())
return true;
return false;
}
void cube_project(void)
{
CUBE c1,c2;
c1.set_l(100);
c1.set_w(100);
c1.set_h(100);
c2.set_l(100);
c2.set_w(100);
c2.set_h(100);
cout << "小项目--c1-长方体的面积是:" << c1.S() << endl;
cout << "小项目--c1-长方体的体积是:" << c1.V() << endl;
if(c1.equality(c2))
cout << "小项目--类内判断--C1和C2是相等的" << endl;
else
cout << "小项目--类内判断--C1和C2是不相等的" << endl;
if (equalityout(c1,c2))
cout << "小项目--类外判断--C1和C2是相等的" << endl;
else
cout << "小项目--类外判断--C1和C2是不相等的" << endl;
}
4.2、对象的初始化和清理
4.2.1、构造函数和析构函数
C++要想完成对象的初始化和清理操作需要使用两个函数:构造函数和析构函数
- 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用
- 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作
构造函数语法: 类名(){}
- 构造函数,没有返回值也不写void
- 函数名称与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
析构函数语法: ~类名(){}
- 析构函数,没有返回值也不写void
- 函数名称与类名相同,在名称前加上符号 ~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
4.2.2、构造函数的分类及调用
两种分类方式
- 按参数分为: 有参构造和无参构造
- 按类型分为: 普通构造和拷贝构造
三种调用方式
- 括号法
- 显示法
- 隐式转换法
#include <iostream>
using namespace std;
class person1
{
public:
person1()
{
cout << "person1的构造函数" << endl;
}
~person1()
{
cout << "person1的析构函数" << endl;
}
};
void func1()
{
person1 per1;
}
/*
构造函数的分类及调用
两种分类方式:
1、按参数分为: 有参构造和无参构造
2、按类型分为: 普通构造和拷贝构造
三种调用方式:
1、括号法
2、显示法
3、隐式转换法*/
class person2
{
public:
//无惨构造
person2()
{
cout << "person2无参构造函数" << endl;
}
person2(int age)
{
cout << "括号法----person2有参构造函数" << endl;
m_age = age;
}
person2(const person2 &p)
{
cout << "person2拷贝构造函数" << endl;
m_age = p.m_age;
}
~person2()
{
cout << "person2析构函数" << endl;
}
public:
int m_age;
};
void func2()
{
person2 p1;//调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明
person2 p2(40); //2.1 括号法,常用
person2 p3(p2);
person2 p4 = person2(80);
person2 p5 = person2(p4);
//注意:不能利用 拷贝构造函数 初始化匿名对象 编译器认为是对象声明
//Person p5(p4);
}
void construc_destruc(void)
{
func1();
func2();
}
4.2.3、拷贝构造函数调用时机
C++中拷贝构造函数调用时机通常有三种情况
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
#include <iostream>
using namespace std;
/*
C++中拷贝构造函数调用时机通常有三种情况
1、使用一个已经创建完毕的对象来初始化一个新对象
2、值传递的方式给函数参数传值
3、以值方式返回局部对象
*/
class person3
{
public:
//无惨构造
person3()
{
cout << "person3无参构造函数" << endl;
}
person3(int age)
{
cout << "括号法----person3有参构造函数 ";
m_age = age;
cout << "年龄 = " << m_age << endl;
}
person3(const person3& p)
{
cout << "person3拷贝构造函数 ";
m_age = p.m_age;
cout << "年龄 = " << m_age << endl;
}
~person3()
{
cout << "person3析构函数" << endl;
}
public:
int m_age;
};
//1、使用一个已经创建完毕的对象来初始化一个新对象
void func4()
{
person3 man(100);
person3 newman(man); //调用拷贝构造函数
person3 newman2 = man; //调用拷贝构造函数
cout << "1、使用一个已经创建完毕的对象来初始化一个新对象" << endl;
}
//2、值传递的方式给函数参数传值
void dowork(person3 p1)
{
cout << "2、值传递的方式给函数参数传值" << endl;
}
void func5()
{
person3 man(100);
dowork(man);
}
//3、以值方式返回局部对象
person3 dowork1()
{
person3 p1(56);
cout << "局部变量p1的地址是:" << (int*)&p1 << endl;
return p1;
}
void func6()
{
person3 man = dowork1();
cout << "返回值---局部变量p1的地址是:" << (int*)&man << endl;
cout << "3、以值方式返回局部对象--age = " << man.m_age << endl;
}
void copytime(void)
{
func4();
func5();
func6();
}
构造函数调用规则:
默认情况下,c++编译器至少给一个类添加3个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
- 如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,c++不会再提供其他构造函数
4.2.4、深拷贝和浅拷贝
- 浅拷贝:简单的赋值拷贝操作
- 深拷贝:在堆区重新申请空间,进行拷贝操作
#include <iostream>
using namespace std;
//浅拷贝:简单的赋值拷贝操作
//深拷贝:在堆区重新申请空间,进行拷贝操作
class person4
{
public:
person4()
{
cout << "person4---无参构造函数!" << endl;
}
person4(int age, int height)
{
cout << "person4---有参构造函数!" << endl;
m_age = age;
m_height = new int(height);
}
person4(const person4 &p)
{
cout << "person4---拷贝构造函数!" << endl;
m_age = p.m_age;
m_height = new int(*p.m_height);
}
//构造函数
~person4()
{
cout << "person4---析构函数" << endl;
if (m_height != NULL)
{
delete m_height;
}
}
public:
int m_age;
int* m_height;
};
void Deep_and_shallow_copy(void)
{
person4 p1(50, 96);
person4 p2(p1);
cout << "p1的年龄: " << p1.m_age << " 身高: " << *p1.m_height << endl;
cout << "p2的年龄: " << p2.m_age << " 身高: " << *p2.m_height << endl;
}
//总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
4.2.5、初始化列表
- 作用:C++提供了初始化列表语法,用来初始化属性
- 语法: 构造函数():属性1(值1),属性2(值2)… {}
#include <iostream>
using namespace std;
class person5
{
public:
person5(int i, float f, string name) :m_i(i), m_f(f), m_name(name)
{
cout << "person5---有参构造函数---初始化列表" << endl;
cout << "person5---m_i = " << m_i << endl;
cout << "person5---m_f = " << m_f << endl;
cout << "person5---m_name = " << m_name << endl;
}
~person5()
{
cout << "person5---有参析构函数---初始化列表" << endl;
}
private:
int m_i;
float m_f;
string m_name;
};
void configlist(void)
{
person5 p(1, 56.65, "王二毛");
}
4.2.6、类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为 对象成员
举例:
class A {}
class B
{
A a;
}
//B类对象中有对象A作为成员,A为对象成员
那么当创建B对象时,A与B的构造和析构的顺序是谁先谁后?
- 当类中成员是其他类对象时,我们称该成员为 对象成员
- 构造的顺序是 :先调用对象成员的构造,再调用本类构造
- 析构顺序与构造相反
#include <iostream>
using namespace std;
class phone
{
public:
phone(string pname)
{
cout << "phone---有参构造函数" << endl;
m_phone = pname;
}
~phone()
{
cout << "phone---有参析构函数" << endl;
}
public:
string m_phone;
};
class person6
{
public:
person6(int age, string name, string pname) :m_age(age), m_name(name), m_pname(pname)
{
cout << "person6---有参构造函数" << endl;
cout << m_age << "岁的" << m_name << "使用的是" << m_pname.m_phone << "牌手机" << endl;
}
~person6()
{
cout << "person6---有参析构函数" << endl;
}
public:
string m_name;
int m_age;
phone m_pname;
};
void object_member(void)
{
person6 p(56, "王二毛", "诺基亚");
}
4.2.7、静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:静态成员变量和静态成员函数
静态成员变量
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
#include <iostream>
using namespace std;
//所有对象共享同一份数据
//在编译阶段分配内存
//类内声明,类外初始化
//静态变量
class staticmemvar
{
public:
//类内声明,类外初始化
//staticmemvar(int a) :m_a(a){}
staticmemvar()
{
cout << "静态成员变量---staticmemvar---有参构造函数" << endl;
cout << "静态成员变量---所有对象共享同一份数据" << endl;
cout << "静态成员变量---也有权限之分" << endl;
}
~staticmemvar()
{
cout << "staticmemvar---有参析构函数" << endl;
}
public:
static int m_a;
private:
static int m_b;
};
int staticmemvar::m_a = 100;
int staticmemvar::m_b = 189;
//1 程序共享一个函数
//2 静态成员函数只能访问静态成员变量
//静态函数
class staticmemfun
{
public:
static void func()
{
m_a = 45;
// m_b = 5355;
// m_c = 10;
cout << "公共权限----func调用" << endl;
cout << "静态成员函数---所有对象共享一个函数" << endl;
cout << "静态成员函数---也有权限之分" << endl;
cout << "静态成员函数---只能访问静态成员变量" << endl;
}
~staticmemfun()
{
cout << "staticmemvar---有参析构函数" << endl;
}
public:
static int m_a;
int m_c;
private:
//静态成员函数也是有访问权限的
static void func2()
{
cout << "私有权限----func2调用" << endl;
}
static int m_b;
};
int staticmemfun::m_a = 1089;
int staticmemfun::m_b = 199;
void staticfunc(void)
{
staticmemvar p1;
p1.m_a = 5632;
cout << "p1.m_a = " << p1.m_a << endl;
// cout << "p1.m_b = " << p1.m_b << endl;//无权限
staticmemvar p2;
p2.m_a = 896454;
cout << "p1.m_a = " << p1.m_a << endl;//共享同一份数据
// cout << "p1.m_b = " << p1.m_b << endl; //无权限
cout << "p2.m_a = " << p2.m_a << endl;
// cout << "p2.m_b = " << p2.m_b << endl;//无权限
//2、通过类名
cout << "m_a = " << staticmemvar::m_a << endl;
// cout << "m_b = " << staticmemvar::m_b << endl;//无权限
staticmemfun f1;
f1.func();
//通过类名
staticmemfun::func();
// staticmemfun::func2();//无权限
}
4.3、C++对象模型和this指针
4.3.1、成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上
class Person
{
public:
Person()
{
mA = 0;
}
//非静态成员变量占对象空间
int mA;
//静态成员变量不占对象空间
static int mB;
//函数也不占对象空间,所有函数共享一个函数实例
void func()
{
cout << "mA:" << this->mA << endl;
}
//静态成员函数也不占对象空间
static void sfunc()
{
}
};
int main()
{
cout << sizeof(Person) << endl;
system("pause");
return 0;
}
4.3.2、this指针概念
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
那么问题是:这一块代码是如何区分那个对象调用自己的呢?
- c++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象
- this指针是隐含每一个非静态成员函数内的一种指针
- this指针不需要定义,直接使用即可
this指针的用途:
- 当形参和成员变量同名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用return *this
class person7
{
public:
person7(int age)
{
cout << "this--person7--有参构造函数" << endl;
this->age = age;
}
person7 &personaddperson(person7 p)
{
this->age += p.age;
//返回自身地址
return *this;
}
public:
int age;
};
void objeck_this(void)
{
person7 p1(45);
cout << "p1.age = " << p1.age << endl;
person7 p2(78);
p2.personaddperson(p1).personaddperson(p1).personaddperson(p1);
cout << "p2.age = " << p2.age << endl;
}
4.3.3、空指针访问成员函数
class person8
{
public:
int age;
public:
void show1()
{
cout << "show1" << endl;
}
void show2()
{
if (this == NULL)//如果没有添加判断,就会报错
return;
cout << "age = " << age << endl;
}
};
void objeck_this(void)
{
person8* p3 = NULL;
p3->show1();
p3->show2();
}
4.3.4、const修饰成员函数
常函数:
- 成员函数后加const后我们称为这个函数为常函数
- 常函数内不可以修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数
class person9
{
public:
int ma;
mutable int mb;
public:
person9()
{
cout << "person9--无参构造函数" << endl;
ma = 0;
mb = 0;
}
void show1() const {
//ma = 100;//ma在定义时没有使用mutable关键字,常函数不能修改成员的属性
//this = NULL;//
mb = 345;//mb在定义时使用了mutable关键字,在常函数中是可以进行修改的
cout << "show1常函数 的 ma = " << this->ma << endl;
cout << "show1常函数 的 mb = " << this->mb << endl;
}
void show2(){
ma = 5547;
mb = 67679;
cout << "show2函数 的 ma = " << this->ma << endl;
cout << "show2函数 的 mb = " << this->mb << endl;
}
};
void objeck_this(void)
{
const person9 p4;//常对象
person9 p5;//常对象
cout << "person9.ma = " << p4.ma << endl;
cout << "person9.ma = " << p4.mb << endl;
//p4.ma = 2245;//常对象不能修改成员变量的值,但是可以访问
p4.mb = 5657;但是常对象可以修改mutable修饰成员变量
//常对象访问成员函数
//p4.show2();//常对象只能调用常函数
p5.show2();
p4.show1();
}
4.4、友元
生活中你的家有客厅(Public),有你的卧室(Private)
客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去
但是呢,你也可以允许你的好闺蜜好基友进去。
在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术
友元的目的就是让一个函数或者类 访问另一个类中私有成员
友元的关键字为 friend
友元的三种实现
- 全局函数做友元
- 类做友元
- 成员函数做友元
#include <iostream>
using namespace std;
/*
友元的三种实现
1、全局函数做友元
2、类做友元
3、成员函数做友元
*/
class mybuilding;
class goodfriend1
{
public:
goodfriend1(mybuilding *bulid, string name);
void visit();
private:
mybuilding *build;
string m_name;
};
class goodfriend2
{
public:
goodfriend2(mybuilding* bulid, string name);
void visit1();
void visit2();
private:
mybuilding* build;
string m_name;
};
class mybuilding
{
//使用friend关键字,说明myfriend全局函数是mybuilding的好朋友
friend void myfriend(mybuilding& building); //1、全局函数-做友元
friend class goodfriend1; //2、类-做友元
friend void goodfriend2::visit1(); //3、成员函数做友元--visit1可以访问,visit2不可以访问
public:
mybuilding(string sittingroom,string bedroom)
{
cout << "mybuilding--有参构造函数" << endl;
cout << "1、全局函数-做友元" << endl;
cout << "2、类-做友元" << endl;
cout << "3、成员函数做友元--visit1可以访问,visit2不可以访问" << endl;
this->m_bedroom = bedroom;
this->m_sittingroom = sittingroom;
}
public:
string m_sittingroom;//客厅
private:
string m_bedroom;//卧室
};
//1
void myfriend(mybuilding& building)
{
cout << "**********************************************************" << endl;
cout << "myfriend--全局函数--做友元" << endl;
cout << "myfriend--全局函数--正在访问:" << building.m_bedroom << endl;
cout << "myfriend--全局函数--正在访问:" << building.m_sittingroom << endl;
}
//2
goodfriend1::goodfriend1(mybuilding *bulid, string name)
{
this->build = bulid;
cout << "**********************************************************" << endl;
cout << "goodfriend1--" << name << "--有参构造函数" << endl;
cout << "goodfriend1--" << name << "--类--做友元" << endl;
this->m_name = name;
}
void goodfriend1::visit()
{
cout << "goodfriend1--visit--" << this->m_name << "--类--正在访问:" << build->m_bedroom << endl;
cout << "goodfriend1--visit--" << this->m_name << "--类--正在访问:" << build->m_sittingroom << endl;
}
//3
goodfriend2::goodfriend2(mybuilding* bulid, string name)
{
this->build = bulid;
cout << "**********************************************************" << endl;
cout << "goodfriend2--" << name << "--有参构造函数" << endl;
cout << "goodfriend2--" << name << "--成员函数--做友元" << endl;
this->m_name = name;
}
void goodfriend2::visit1()
{
cout << "goodfriend2--visit1--" << this->m_name << "--成员函数--正在访问:" << build->m_bedroom << endl;
cout << "goodfriend2--visit1--" << this->m_name << "--成员函数--正在访问:" << build->m_sittingroom << endl;
}
void goodfriend2::visit2()
{
// cout << "goodfriend2--visit2--" << this->m_name << "--成员函数--正在访问:" << build->m_bedroom << endl;
cout << "goodfriend2--visit2--" << this->m_name << "--成员函数--正在访问:" << build->m_sittingroom << endl;
}
void friendtest(void)
{
mybuilding b1("客厅","卧室");//定义
//1
myfriend(b1);//全局函数做友元
//2
goodfriend1 g1(&b1, "王二毛");//类做友元
g1.visit();
//3
goodfriend2 g2(&b1, "王三毛");//成员函数做友元
g2.visit1();
g2.visit2();
}
4.5、运算符重载
- 运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
4.5.1、加号运算符重载
注意:
- 对于内置的数据类型的表达式的的运算符是不可能改变的
- 不要滥用运算符重载
#include <iostream>
using namespace std;
class person10
{
public:
person10()
{
cout << "perosn10--加号运算符重载--无参构造函数" << endl;
}
person10(int a, int b)
{
cout << "perosn10--加号运算符重载--有参构造函数" << endl;
this->m_a = a;
this->m_b = b;
}
/*person10 operator+(person10& p)
{
person10 temp;
temp.m_a = this->m_a + p.m_a;
temp.m_b = this->m_b + p.m_b;
return temp;
}*/
public:
int m_a;
int m_b;
};
person10 operator+(person10 &p1, person10 &p2)
{
person10 temp;
temp.m_a = p1.m_a + p2.m_a;
temp.m_b = p1.m_b + p2.m_b;
return temp;
}
person10 operator+(person10& p1, int num)
{
person10 temp;
temp.m_a = p1.m_a + num;
temp.m_b = p1.m_b + num;
return temp;
}
void add_operator_overloading(void)
{
person10 p1(10, 56);
person10 p2(20, 86);
person10 p3 = p1 + p2;
//成员函数加号运算符重载调用本质
//person10 p3 = p1.operator+(p2);
//全局函数加号运算符重载调用本质
//person10 p4 = operator+(p1, p2);
//运算符重载、也可以发生函数重载
//person10 p4 = operator+(p1, 100);
person10 p4 = p1 + 100;
cout << "加号运算符重载--p3.m_a = " << p3.m_a << endl;
cout << "加号运算符重载--p3.m_b = " << p3.m_b << endl;
cout << "加号运算符重载--p4.m_a = " << p4.m_a << endl;
cout << "加号运算符重载--p4.m_b = " << p4.m_b << endl;
}
4.5.2、左移运算符重载
作用:可以输出自定义数据类型
注意:
- 重载左移运算符配合友元可以实现输出自定义数据类型
一般情况下cout可以输出整形、浮点型、等等等等
class person
{
public:
int m_a;
int m_b;
}
person p;
cout << p;//就会报错
#include <iostream>
using namespace std;
class person11
{
friend ostream& operator<<(ostream& out, person11& p);
public:
person11(int a, int b)
{
cout << "person11--左移运算符--有参构造函数" << endl;
m_a = a;
m_b = b;
}
//成员函数实现不了 p << cout 不是我们想要的效果
//void operator<<(Person& p){
//}
private:
int m_a;
int m_b;
};
ostream& operator<<(ostream &out, person11 &p)
{
out << "a = " << p.m_a << endl;
out << "b = " << p.m_b;
return out;
}
void leftshift_operator_overloading(void)
{
person11 p(45,798);
cout << p << endl << "hello" << endl;
}
4.5.3、递增、递减运算符重载
作用: 通过重载递增运算符,实现自己的整型数据
注意:
- 前置递增返回引用,后置递增返回值
#include <iostream>
using namespace std;
class person12
{
friend ostream& operator<<(ostream& out, person12 p);
public:
person12()
{
cout << "person12--递增运算符重载--无参构造函数" << endl;
m_num = 5;
}
//前缀递增++
person12& operator++()
{
m_num++;
return *this;
}
//后缀递增++
person12 operator++(int)
{
//记录当前本身的值
person12 temp = *this;
//然后让本身的值加1
m_num++;
//但是返回的是以前的值,达到先返回后++;
return temp;
}
//前缀递减--
person12& operator--()
{
m_num--;
return *this;
}
//后缀递增++
person12 operator--(int)
{
//记录当前本身的值
person12 temp = *this;
//然后让本身的值减1
m_num--;
//但是返回的是以前的值,达到先返回后++
return temp;
}
private:
int m_num;
};
//左移运算符
ostream& operator<<(ostream& out, person12 p)
{
out << "p.m_num = " << p.m_num;
return out;
}
void increase_operator_overloading(void)
{
person12 p1;
person12 p2;
person12 p3;
person12 p4;
cout << "p1.m_num = " << ++p1 << endl;
cout << "p1.m_num = " << p1 << endl;
cout << "p2.m_num = " << p2++ << endl;
cout << "p2.m_num = " << p2 << endl;
cout << "p3.m_num = " << --p3 << endl;
cout << "p3.m_num = " << p3 << endl;
cout << "p4.m_num = " << p4-- << endl;
cout << "p4.m_num = " << p4 << endl;
}
4.5.4、赋值运算符重载
c++编译器至少给一个类添加4个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
- 赋值运算符operator,对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
#include <iostream>
using namespace std;
class person13
{
public:
person13(int age)
{
cout << "person13--赋值运算符重载--有参构造函数" << endl;
m_age = new int(age);
}
~person13()
{
if (m_age != NULL)
{
delete m_age;
m_age = NULL;
}
cout << "person13--赋值运算符重载--有参析构函数" << endl;
}
//赋值操作-返回的是引用
person13& operator=(person13 &p)
{
//先判断堆区的地址中是不是空
if (m_age != NULL)
{
delete m_age;
m_age = NULL;
}
//编译器提供的代码是浅拷贝--会出现地址重复delete,错误
//m_age = p.m_age;
//提供深拷贝 解决浅拷贝的问题
m_age = new int(*p.m_age);
return *this;
}
public:
int* m_age;
};
void assigning_operator(void)
{
person13 p1(89);
person13 p2(45);
person13 p3(989);
p3 = p2 = p1;
cout << "p1的年龄为:" << *p1.m_age << endl;
cout << "p2的年龄为:" << *p2.m_age << endl;
cout << "p3的年龄为:" << *p3.m_age << endl;
}
4.5.5、关系运算符重载
作用:重载关系运算符,可以让两个自定义类型对象进行对比操作
#include <iostream>
using namespace std;
class person14
{
public:
person14(string name, int age)
{
cout << "person14--关系运算符重载--有参构造函数" << endl;
m_name = name;
m_age = age;
}
~person14()
{
cout << "person14--关系运算符重载--析构函数" << endl;
}
bool operator==(person14 &p)
{
if (this->m_age == p.m_age && this->m_name == p.m_name)
return true;
else
return false;
}
bool operator!=(person14& p)
{
if (this->m_age == p.m_age && this->m_name == p.m_name)
return false;
else
return true;
}
public:
string m_name;
int m_age;
};
void relational_operator(void)
{
person14 p1("王二毛", 5986);
person14 p2("王二毛", 5986);
person14 p3("王二毛", 34);
person14 p4("王二毛", 596);
if (p1 == p2)
cout << "p1和p2相等" << endl;
else
cout << "p1和p2不相等" << endl;
if (p3 == p4)
cout << "p3和p4相等" << endl;
else
cout << "p3和p4不相等" << endl;
}
4.5.6、函数调用运算符重载
- 函数调用运算符 () 也可以重载
- 由于重载后使用的方式非常像函数的调用,因此称为仿函数
- 仿函数没有固定写法,非常灵活
#include <iostream>
using namespace std;
class function1
{
public:
void operator()(string massage)//也可以发生重载--参数不一样即可
{
cout << massage << endl;
}
int operator()(int a,int b)
{
return (a + b);
}
};
void Func(string massage)
{
cout << massage << endl;
}
void func_operator(void)
{
function1 f1;
f1("函数调用运算符重载--大家好!我叫王二毛");
Func("函数调用--大家好!我叫王三毛");
cout << f1(34, 56) << endl;
//匿名对象调用
cout << function1()(69, 31) << endl;
}
4.6、继承
继承是面向对象三大特性之一
有些类与类之间存在特殊的关系,例如下图中:
我们发现,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。
这个时候我们就可以考虑利用继承的技术,减少重复代码
4.6.1、继承的基本语法
例如我们看到很多网站中,都有公共的头部,公共的底部,甚至公共的左侧列表,只有中心内容不同
接下来我们分别利用普通写法和继承的写法来实现网页中的内容,看一下继承存在的意义以及好处
普通实现:
#include <iostream>
using namespace std;
//Java页面
class Java
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,C++...(公共分类列表)" << endl;
}
void content()
{
cout << "JAVA学科视频" << endl;
}
};
//Python页面
class Python
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}void left()
{
cout << "Java,Python,C++...(公共分类列表)" << endl;
}
void content()
{ cout << "Python学科视频" << endl;
}
};
//C++页面
class CPP
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,C++...(公共分类列表)" << endl;
}
void content()
{
cout << "C++学科视频" << endl;
}
};
void inherit_basic(void)
{
//Java页面
cout << "Java下载视频页面如下: " << endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout << "--------------------" << endl;
//Python页面
cout << "Python下载视频页面如下: " << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();
cout << "--------------------" << endl;
//C++页面
cout << "C++下载视频页面如下: " << endl;
CPP cp;
cp.header();
cp.footer();
cp.left();
cp.content();
}
继承实现:
//公共页面
class BasePage
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,C++...(公共分类列表)" << endl;
}
};
//Java页面
class java : public BasePage
{
public:
void content()
{
cout << "JAVA学科视频" << endl;
}
};
//python界面
class python : public BasePage
{
public:
void content()
{
cout << "Python科学视频" << endl;
}
};
//C++界面
class cpp : public BasePage
{
public:
void content()
{
cout << "C++科学视频" << endl;
}
};
void inherit_basic(void)
{
//Java页面
cout << "Java下载视频页面如下: " << endl;
java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout << "--------------------" << endl;
//Python页面
cout << "Python下载视频页面如下: " << endl;
python py;
py.header();
py.footer();
py.left();
py.content();
cout << "--------------------" << endl;
//C++页面
cout << "C++下载视频页面如下: " << endl;
cpp cp;
cp.header();
cp.footer();
cp.left();
cp.content();
}
总结:
- 继承的好处:可以减少重复的代码
class A : public B;
A 类称为子类 或 派生类
B 类称为父类 或 基类
派生类中的成员,包含两大部分:
- 一类是从基类继承过来的,
- 一类是自己增加的成员。
从基类继承过过来的表现其共性,而新增的成员体现了其个性。
4.6.2、继承方式
继承的语法: class 子类 : 继承方式 父类
继承方式一共有三种:
- 公共继承
- 保护继承
- 私有继承
- 由图可知,不管是什么继承,子类都不能访问父类的私有权限
- 公共继承方式下,子类中除了不能访问父类的私有权限,子类中的其他权限不会发送改变,父类是public,子类也是public,父类是protected,子类也是protected
- 保护继承方式下,子类中除了不能访问父类的私有权限,子类中的其他权限都会变成保护权限:父类是public,子类变成protected,父类是protected,子类也是protected
- 私有继承方式下,子类中除了不能访问父类的私有权限,子类中的其他权限都会变成私有权限:父类是public,子类变成private,父类是protected,子类变成private
具体例子:
#include <iostream>
using namespace std;
class base
{
public:
int m_a;
protected:
int m_b;
private:
int m_c;
};
//公共继承
class sun1 :public base
{
public:
void func()
{
m_a = 100;//m_a在父类中是公共权限,子类中是公共权限
m_b = 200;//m_b在父类中是保护权限,子类中是保护权限
//m_c = 300;//m_c在父类中是私有权限,子类不可访问
}
};
//保护继承
class sun2 :protected base
{
public:
void func()
{
m_a = 100;//m_a在父类中是公共权限,子类中变成保护权限
m_b = 200;//m_b在父类中是保护权限,子类中变成保护权限
//m_c = 300;//m_c在父类中是私有权限,子类不可访问
}
};
//私有继承
class sun3 :private base
{
public:
void func()
{
m_a = 100;//m_a在父类中是公共权限,子类中变成私有权限
m_b = 200;//m_b在父类中是保护权限,子类中变成私有权限
//m_c = 300;//m_c在父类中是私有权限,子类不可访问
}
};
//子类grandsun3 公共继承 父类sun3
class grandsun3 :public sun3
{
public:
void func()
{
//m_a = 21000;//m_a在sun3中是私有权限,所有子类grandsun3是没有权限访问的
//m_b = 324424;//m_b在sun3中是私有权限,所有子类grandsun3是没有权限访问的
}
};
void inherit_method(void)
{
//公共继承
sun1 s1;
s1.m_a = 10000;//sun1中的m_a是公共权限,类外可以访问
//s1.m_b = 10000;//sun1中的m_b是保护权限,类外不可以访问
//s1.m_c = 10000;//sun1中的m_c是私有权限,类外不可以访问
//保护继承
sun2 s2;
//s2.m_a = 10000;//sun2中的m_a是保护权限,类外不能访问
//s2.m_b = 10000;//sun2中的m_b是保护权限,类外不能访问
//s2.m_c = 10000;//sun2中的m_c是私有权限,类外不能访问
//私有继承
sun3 s3;
//s3.m_a = 10000;//sun3中的m_a是私有权限,类外不能访问
//s3.m_b = 10000;//sun3中的m_b是私有权限,类外不能访问
//s3.m_c = 10000;//sun3中的m_c是私有权限,类外不能访问
}
4.6.3、继承中的对象模型
问题:从父类继承过来的成员,哪些属于子类对象中?
#include <iostream>
using namespace std;
class Base {
public:
int m_A;
protected:
int m_B;
private:
int m_C; //私有成员只是被隐藏了,但是还是会继承下去
};
//公共继承
class Son :public Base
{
public:
int m_D;
};
void test01()
{
cout << "sizeof Son = " << sizeof(Son) << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
利用工具查看:
结论: 父类中私有成员也是被子类继承下去了,只是由编译器给隐藏后访问不到
4.6.4、继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数
问题:父类和子类的构造和析构顺序是谁先谁后?
栗子:
#include <iostream>
using namespace std;
class base1
{
public:
base1()
{
cout << "父类base1---无参构造函数" << endl;
}
~base1()
{
cout << "父类base1---无参析构函数" << endl;
}
};
class son4 :public base1
{
public:
son4()
{
cout << "子类son4---无参构造函数" << endl;
}
~son4()
{
cout << "子类son4---无参析构函数" << endl;
}
};
void inherit_order(void)
{
son4 s1;
}
总结:继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
4.6.5、继承同名成员处理方式
问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?
- 访问子类同名成员 直接访问即可
- 访问父类同名成员 需要加作用域
#include <iostream>
using namespace std;
class base3
{
public:
base3()
{
cout << "父类base3---无参构造函数" << endl;
m_a = 438975754390;
m_b = 0;
}
void func()
{
cout << "父类base3---func()函数调用" << endl;
}
void func(int a)
{
cout << "父类base3---func(int a)函数调用" << endl;
}
public:
int m_a;
int m_b;
};
class son6: public base3
{
public:
son6()
{
cout << "子类son6---无参构造函数" << endl;
m_a = 100;
m_b = 46456l;
}
//当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数
//如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域
void func()
{
cout << "子类son6---func()函数调用" << endl;
}
public:
int m_a;
int m_b;
};
void inherit_samename(void)
{
son6 s;
cout << "子类son6的m_a:" << s.m_a << endl;
cout << "父类base3的m_a:" << s.base3::m_a << endl;
s.func();
s.base3::func();
s.base3::func(4545);
}
总结:
- 子类对象可以直接访问到子类中同名成员
- 子类对象加作用域可以访问到父类同名成员
- 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中
同名函数
4.6.6、继承同名静态成员处理方式
问题:继承中同名的静态成员在子类对象上如何进行访问?
静态成员和非静态成员出现同名,处理方式一致
- 访问子类同名成员 直接访问即可
- 访问父类同名成员 需要加作用域
#include <iostream>
using namespace std;
class base4
{
public:
base4()
{
cout << "父类base4---无参构造函数" << endl;
/*m_a = 454390;
m_b = 0;*/
}
static void func()
{
cout << "父类base4---func()函数调用" << endl;
}
static void func(int a)
{
cout << "父类base4---func(int a)函数调用" << endl;
}
public:
static int m_a;
static int m_b;
};
int base4::m_a = 34;
int base4::m_b = 89;
class son7 : public base4
{
public:
son7()
{
cout << "子类son7---无参构造函数" << endl;
/*m_a = 100;
m_b = 46456l;*/
}
//当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数
//如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域
static void func()
{
cout << "子类son7---func()函数调用" << endl;
}
public:
static int m_a;
static int m_b;
};
int son7::m_a = 5465;
int son7::m_b = 4354;
void inherit_samename_static(void)
{
cout << "通过对象访问" << endl;
son7 s;
cout << "子类son7的m_a:" << s.m_a << endl;
cout << "父类base4的m_a:" << s.base4::m_a << endl;
s.func();
s.base4::func();
s.base4::func(4545);
//出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问
cout << "通过类名访问" << endl;
cout << "子类son7的m_a:" << son7::m_a << endl;
cout << "父类base4的m_a:" << son7::base4::m_a << endl;
son7::func();
son7::base4::func();
son7::base4::func(676);
}
总结:同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象 和 通
过类名)
4.6.7、多继承语法
C++允许一个类继承多个类
语法:class 子类 :继承方式 父类1 , 继承方式 父类2…
多继承可能会引发父类中有同名成员出现,需要加作用域区分
C++实际开发中不建议用多继承
#include <iostream
using namespace std;
class base5
{
public:
base5()
{
cout << "父类base5---无参构造函数" << endl;
m_a = 43897;
m_b = 890;
}
~base5()
{
cout << "父类base5---无参析构函数" << endl;
}
void func()
{
cout << "父类base5---func()函数调用" << endl;
}
void func(int a)
{
cout << "父类base5---func(int a)函数调用" << endl;
}
public:
int m_a;
int m_b;
};
class base6
{
public:
base6()
{
cout << "父类base6---无参构造函数" << endl;
m_a = 234;
m_b = 56;
}
~base6()
{
cout << "父类base6---无参析构函数" << endl;
}
void func()
{
cout << "父类base6---func()函数调用" << endl;
}
void func(int a)
{
cout << "父类base6---func(int a)函数调用" << endl;
}
public:
int m_a;
int m_b;
};
//多继承的构造函数的顺序是,先base5->base6->son8
//析构函数相反
class son8 : public base5, public base6
{
public:
son8()
{
cout << "子类son8---无参构造函数" << endl;
m_a = 100;
m_b = 56l;
}
~son8()
{
cout << "子类son8---无参析构函数" << endl;
}
//当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数
//如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域
void func()
{
cout << "子类son8---func()函数调用" << endl;
}
public:
int m_a;
int m_b;
};
void inherit_samename_more(void)
{
son8 s;
cout << "子类son8的m_a:" << s.m_a << endl;
cout << "父类base5的m_a:" << s.base5::m_a << endl;
cout << "父类base6的m_a:" << s.base6::m_a << endl;
s.func();
s.base5::func();
s.base5::func(4545);
s.base6::func();
s.base6::func(78);
}
总结: 多继承中如果父类中出现了同名情况,子类使用时候要加作用域
4.6.8、菱形继承
菱形继承概念:
- 两个派生类继承同一个基类
- 又有某个类同时继承者两个派生类
- 这种继承被称为菱形继承,或者钻石继承
菱形继承问题:
- 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
- 草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。
本例程使用了:virtual关键字
#include <iostream>
using namespace std;
//动物
class animal
{
public:
animal()
{
cout << "父类animal---无参构造函数" << endl;
m_age = 345;
}
~animal()
{
cout << "父类animal---无参析构函数" << endl;
}
void func()
{
cout << "父类animal---func()函数调用" << endl;
}
void func(int a)
{
cout << "父类animal---func(int a)函数调用" << endl;
}
public:
int m_age;
};
//继承前加virtual关键字后,变为虚继承
//此时公共的父类Animal称为虚基类
class tuo : virtual public animal
{
public:
tuo()
{
cout << "父类tuo---无参构造函数" << endl;
m_age = 234;
}
~tuo()
{
cout << "父类tuo---无参析构函数" << endl;
}
void func()
{
cout << "父类tuo---func()函数调用" << endl;
}
void func(int a)
{
cout << "父类tuo---func(int a)函数调用" << endl;
}
public:
int m_age;
};
//继承前加virtual关键字后,变为虚继承
//此时公共的父类Animal称为虚基类
class sheep : virtual public animal
{
public:
sheep()
{
cout << "父类sheep---无参构造函数" << endl;
m_age = 234;
}
~sheep()
{
cout << "父类sheep---无参析构函数" << endl;
}
void func()
{
cout << "父类sheep---func()函数调用" << endl;
}
void func(int a)
{
cout << "父类sheep---func(int a)函数调用" << endl;
}
public:
int m_age;
};
class sheeptuo : public sheep, public tuo
{
public:
sheeptuo()
{
cout << "子类sheeptuo---无参构造函数" << endl;
m_age = 100;
}
~sheeptuo()
{
cout << "子类sheeptuo---无参析构函数" << endl;
}
//当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数
//如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域
void func()
{
cout << "子类sheeptuo---func()函数调用" << endl;
}
public:
int m_age;
};
void diamond_inheritance(void)
{
sheeptuo s;
s.m_age = 85;
s.sheep::m_age = 13;
s.tuo::m_age = 38;
s.sheep::animal::m_age = 67;
s.tuo::animal::m_age = 47;
cout << "子类---s.m_age:" << s.m_age << endl;
cout << "小父类---s.sheep::m_age:" << s.sheep::m_age << endl;
cout << "小父类---s.tuo::m_age:" << s.tuo::m_age << endl;
cout << "总父类--动物--s.sheep::animal::m_age:" << s.sheep::animal::m_age << endl;
cout << "总父类--动物--s.tuo::animal::m_age:" << s.tuo::animal::m_age << endl;
s.func();
s.sheep::func();
s.sheep::func(56);
s.tuo::func();
s.tuo::func(78);
s.sheep::animal::func();
s.sheep::animal::func(497);
s.tuo::animal::func();
s.tuo::animal::func(245);
}
没有使用:virtual关键字的结果是
总结:
- 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义
- 利用虚继承virtual可以解决菱形继承问题
4.7、多态
4.7.1、多态的基本语法
多态是C++面向对象三大特性之一
多态分为两类
- 静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
- 动态多态: 派生类和虚函数实现运行时多态
静态多态和动态多态区别:
- 静态多态的函数地址早绑定 - 编译阶段确定函数地址
- 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
下面通过案例进行讲解多态
#include <iostream>
using namespace std;
class animal1
{
public:
//Speak函数就是虚函数
//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
//如果没有使用virtual关键字,本节内容打印的就是:
//动物在说话
//动物在说话
virtual void speak()
{
cout << "动物正在说话" << endl;
}
};
class cat : public animal1
{
public:
void speak()
{
cout << "猫正在说话" << endl;
}
};
class dog : public animal1
{
public:
void speak()
{
cout << "狗正在说话" << endl;
}
};
//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编:父类中--speak--没有添加--virtual--就是静态的
//如果函数地址在运行阶段才能确定,就是动态联编:父类中--speak--添加--virtual--就是动态的
void dospeak(animal1 &a)
{
a.speak();
}
//多态满足条件:
//1、有继承关系
//2、子类重写父类中的虚函数
//多态使用:
//父类指针或引用指向子类对象
void polymorphic_base(void)
{
cat c;
dospeak(c);
dog d;
dospeak(d);
}
总结:
多态满足条件
- 有继承关系
- 子类重写父类中的虚函数
多态使用条件
- 父类指针或引用指向子类对象
重写:函数返回值类型 函数名 参数列表 完全一致称为重写
4.7.2、计算机小项目
案例描述:分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类
多态的优点:
- 代码组织结构清晰
- 可读性强
- 利于前期和后期的扩展以及维护
#include <iostream>
using namespace std;
//父类
//多态实现
//抽象计算器类
//多态优点:代码组织结构清晰,可读性强,利于前期和后期的扩展以及维护
class calculator
{
public:
virtual int result()
{
return 0;
}
public:
int num1;
int num2;
};
class addcalculator : public calculator
{
public:
int result()
{
return num1 + num2;
}
};
class subcalculator : public calculator
{
public:
int result()
{
return num1 - num2;
}
};
class mulcalculator : public calculator
{
public:
int result()
{
return num1 * num2;
}
};
class divcalculator : public calculator
{
public:
int result()
{
return num1 / num2;
}
};
void polymorphic_project1(void)
{
//加法
calculator* abc = new addcalculator;
abc->num1 = 34;
abc->num2 = 78;
cout << abc->num1 << "+" << abc->num2 << "=" << abc->result() << endl;
delete abc;
//减法
abc = new subcalculator;
abc->num1 = 34;
abc->num2 = 78;
cout << abc->num1 << "-" << abc->num2 << "=" << abc->result() << endl;
delete abc;
//乘法
abc = new mulcalculator;
abc->num1 = 24;
abc->num2 = 5;
cout << abc->num1 << "*" << abc->num2 << "=" << abc->result() << endl;
delete abc;
//除法
abc = new divcalculator;
abc->num1 = 100;
abc->num2 = 23;
cout << abc->num1 << "/" << abc->num2 << "=" << abc->result() << endl;
delete abc;
}
总结:C++开发提倡利用多态设计程序架构,因为多态优点很多
4.7.3、纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法: virtual 返回值类型 函数名(参数列表) = 0
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
#include <iostream>
using namespace std;
/*纯虚函数的写法:virtual 数据类型 函数名 = 0;*/
class base7
{
public:
//纯虚函数
//类中只要有一个纯虚函数就称为抽象类
//抽象类无法实例化对象
//子类必须重写父类中的纯虚函数,否则也属于抽象类
virtual void fun() = 0;
public:
int num;
};
class son9 :public base7
{
public:
void fun()
{
cout << "纯虚函数--抽象类--bsae7--son9--func函数调用" << endl;
}
};
void Purevirtual_func(void)
{
base7 *s = NULL;
//base = new Base; // 错误,抽象类无法实例化对象
s = new son9;
s->fun();
delete s;
}
4.7.4、多态案例二-制作饮品
案例描述:制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料
利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶
#include <iostream>
using namespace std;
//制作流程
class makingprocess
{
public:
//烧水
virtual void boil() = 0;
//冲泡咖啡
virtual void brew() = 0;
//倒入杯中
virtual void pour() = 0;
//加入辅料
virtual void addaccessories() = 0;
void allprocess()
{
boil();
brew();
pour();
addaccessories();
}
};
class makecoffee : public makingprocess
{
public:
void boil()
{
cout << "烧水" << endl;
}
void brew()
{
cout << "冲泡" << endl;
}
void pour()
{
cout << "倒入" << endl;
}
void addaccessories()
{
cout << "加入牛奶" << endl;
}
};
class maketea : public makingprocess
{
public:
void boil()
{
cout << "烧水" << endl;
}
void brew()
{
cout << "冲泡" << endl;
}
void pour()
{
cout << "倒入" << endl;
}
void addaccessories()
{
cout << "加入柠檬" << endl;
}
};
void makingdrink(void)
{
makingprocess* drink = new makecoffee;
drink->allprocess();
delete drink;
cout << "-----------------------------" << endl;
drink = new maketea;
drink->allprocess();
delete drink;
}
4.7.5、虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
虚析构和纯虚析构区别:
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:virtual ~类名(){}
纯虚析构语法:virtual ~类名() = 0;
初始化:类名::~类名(){}
#include <iostream>
using namespace std;
class animal2
{
public:
animal2()
{
cout << "animal2--无参构造函数调用" << endl;
}
virtual ~animal2() = 0;
virtual void speak() = 0;
//析构函数加上virtual关键字,变成虚析构函数
/*virtual ~animal2()
{
cout << "animal2虚析构函数调用!" << endl;
}*/
};
animal2::~animal2()
{
cout << "animal2--纯虚析构函数调用" << endl;
}
//和包含普通纯虚函数的类一样,包含了纯虚析构函数的类也是一个抽象类。不能够被实例化。
class cow :public animal2
{
public:
cow(string name)
{
cout << "cow--有参构造函数" << endl;
this->name = new string(name);
}
~cow()
{
cout << "cow--无参析构函数" << endl;
if (name != NULL)
{
delete name;
name = NULL;
}
}
void speak()
{
cout << *name << "正在说话" << endl;
}
public:
string* name;
};
void purevirtual_destructor(void)
{
animal2 *a = NULL;
a = new cow("王二毛");
a->speak();
//通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄漏
//怎么解决?给基类增加一个虚析构函数
//虚析构函数就是用来解决通过父类指针释放子类对象
delete a;
}
总结:
- 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
- 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
- 拥有纯虚析构函数的类也属于抽象类
4.7.6、小项目-电脑组装
案例描述:
电脑主要组成部件为 CPU(用于计算),显卡(用于显示),内存条(用于存储)
将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例如Intel厂商和Lenovo厂商
创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口
测试时组装三台不同的电脑进行工作
#include <iostream>
using namespace std;
//cpu 显卡 内存
class cpu
{
public:
virtual void calculator() = 0;
};
class card
{
public:
virtual void display() = 0;
};
class memory
{
public:
virtual void storage() = 0;
};
//继承--多态--实现
class intercpu :public cpu
{
public:
void calculator()
{
cout << "inter的CPU正在计算" << endl;
}
};
class intercard :public card
{
public:
void display()
{
cout << "inter的显卡正在显示图像" << endl;
}
};
class intermemory :public memory
{
public:
void storage()
{
cout << "inter的内存正在存储文件" << endl;
}
};
class amdcpu :public cpu
{
public:
void calculator()
{
cout << "amd的CPU正在计算" << endl;
}
};
class amdcard :public card
{
public:
void display()
{
cout << "amd的显卡正在显示图像" << endl;
}
};
class amdmemory :public memory
{
public:
void storage()
{
cout << "amd的内存正在存储文件" << endl;
}
};
//电脑主体
class computer
{
public:
computer(cpu *cpuname, card *cardname, memory *memoryname)
{
cout << "computer--有参构造函数" << endl;
this->cpuname = cpuname;
this->cardname = cardname;
this->memoryname = memoryname;
}
~computer()
{
cout << "computer--析构函数" << endl;
if (cpuname != NULL)
{
delete cpuname;
cpuname = NULL;
}
if (cardname != NULL)
{
delete cardname;
cardname = NULL;
}
if (memoryname != NULL)
{
delete memoryname;
memoryname = NULL;
}
}
void computerwork()
{
cpuname->calculator();
cardname->display();
memoryname->storage();
}
private:
cpu *cpuname;
card* cardname;
memory *memoryname;
};
void computer_package(void)
{
computer *com = NULL;
//第一台电脑
cpu *cpu1 = new intercpu;
card *card1 = new intercard;
memory *memory1 = new intermemory;
com = new computer(cpu1, card1, memory1);
com->computerwork();
delete com;
cout << "----------------------------------------" << endl;
//第二台电脑
cpu* cpu2 = new intercpu;
card* card2 = new amdcard;
memory* memory2 = new amdmemory;
com = new computer(cpu2, card2, memory2);
com->computerwork();
delete com;
}
5、文件
程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放
通过文件可以将数据持久化
C++中对文件操作需要包含头文件 < fstream >
文件类型分为两种:
- 文本文件 - 文件以文本的ASCII码形式存储在计算机中
- 二进制文件 - 文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们
操作文件的三大类:
- ofstream:写操作
- ifstream: 读操作
- fstream : 读写操作
5.1、文本文件
写文件步骤如下:
- 包含头文件
#include <fstream> - 创建流对象
ofstream ofs; - 打开文件
ofs.open(“文件路径”,打开方式); - 写数据
ofs << “写入的数据”; - 关闭文件
ofs.close();
读文件与写文件步骤相似,但是读取方式相对于比较多
读文件步骤如下:
- 包含头文件
#include <fstream> - 创建流对象
ifstream ifs; - 打开文件并判断文件是否打开成功
ifs.open(“文件路径”,打开方式); - 读数据
四种方式读取 - 关闭文件
ifs.close();
#include<iostream>
#include<fstream>
#include <string>
using namespace std;
void files_write(void)
{
//创建流对象
ofstream ofs;
ofs.open("filestest01.txt", ios::out);
ofs << "大家好!我是王二毛!"<<endl;
ofs.close();
}
void files_read(void)
{
//创建流对象
ifstream ifs;
ifs.open("filestest01.txt", ios::in);
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
return;
}
//第一种方式
/*char buf[1024] = {0};
while (ifs >> buf)
{
cout << buf << endl;
}*/
//第二种
/*char buf[1024] = { 0 };
while (ifs.getline(buf,sizeof(buf)))
{
cout << buf << endl;
}*/
//第三种
/*string buf;
while (getline(ifs, buf))
{
cout << buf << endl;
}*/
//第四种
char c;
while ((c = ifs.get()) != EOF)
{
cout << c;
}
ifs.close();
}
5.2、二进制文件
以二进制的方式对文件进行读写操作
打开方式要指定为 ios::binary
写文件
二进制方式写文件主要利用流对象调用成员函数write
函数原型 : ostream& write(const char * buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
读文件
二进制方式读文件主要利用流对象调用成员函数read
函数原型: istream& read(char *buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
#include<iostream>
#include<fstream>
using namespace std;
class person15
{
public:
char m_name[64];
int m_age;
};
void write_binary(void)
{
//1、包含头文件
//2、创建输出流对象
ofstream ofs("person.txt", ios::out | ios::binary);
//3、打开文件
//ofs.open("person.txt", ios::out | ios::binary);
person15 p = { "王二毛",456 };
//4、写文件
ofs.write((const char *)&p,sizeof(p));
//5、关闭文件
ofs.close();
}
void read_binary(void)
{
//1、包含头文件
//2、创建输出流对象
ifstream ifs("person.txt", ios::in | ios::binary);
//3、打开文件
//ofs.open("person.txt", ios::out | ios::binary);
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
}
person15 p1;
//4、写文件
ifs.read((char*)&p1, sizeof(p1));
cout << "姓名: " << p1.m_name << " 年龄: " << p1.m_age << endl;
//5、关闭文件
ifs.close();
}
总结:
- 文件输出流对象 可以通过write函数,以二进制方式写数据
- 文件输入流对象 可以通过read函数,以二进制方式读数据