5 类和对象
5.1 类的基本概念
- MyTeacher.h
#pragma once //只包含一次,和下面宏的作用相同
//这里宏的作用 防止重复导入
//#ifndef C_PLUS_STUDY_MYTEACHER_H
//#define C_PLUS_STUDY_MYTEACHER_H
//类的声明
class MyTeacher {
private:
int m_age;
char m_name[32];
public:
void setAge(int age);
int getAge();
};
//#endif //C_PLUS_STUDY_MYTEACHER_H
- MyTeacher.cpp
#include "../includes/MyTeacher.h"
/**
* MyTeacher:: 定义作用域后,可使用该类的成员属性
* @param age
*/
void MyTeacher::setAge(int age) {
m_age =age;
}
int MyTeacher::getAge() {
return m_age;
}
- 使用
#include <iostream>
#include "../includes/MyTeacher.h"//引入MyTeacher.h头文件
using namespace std;
int a;
void main01() {
cout << "C++类的使用" << endl;
MyTeacher t1;
t1.setAge(33);
cout << "t1.age: " << t1.getAge() << endl;
cout << "========================================" << endl;
float a;
a = 3.14;
// :: 作用域限定运算符
::a = 5;//实现在局部变量a的作用域范围内对全局变量a的访问
cout << "local a: " << a << " global a: " << ::a << endl;
}
5.2 类的封装
- 封装(Encapsulation)
- 类成员的访问控制
继承方式 | 基类的public成员 | 基类的protected成员 | 基类的private成员 | 继承引起的访问控制关系变化概括 |
---|---|---|---|---|
public继承 | 仍为public成员 | 仍为protected成员 | 不可见 | 基类的非私有成员在子类的访问属性不变 |
protected继承 | 变为protected成员 | 变为protected成员 | 不可见 | 基类的非私有成员都为子类的保护成员 |
private继承 | 变为private成员 | 变为private成员 | 不可见 | 基类中的非私有成员都称为子类的私有成员 |
- struct和class关键字区别
5.3 对象的构造和析构
- 构造函数
- 没有返回值
- 函数名称和类名相同
ClassName(){}
- 析构函数
- 没有返回值
- 函数名称和类名相同
~ClassName(){}
5.3.1 构造函数的分类及调用
- 无参数构造函数
- 有参构造函数
- 拷贝构造函数调用时机
- 默认构造函数
/**
* 构造函数的分类
*/
class Test2 {
private:
int m_a;
int m_b;
public:
Test2() {//无参数构造函数
m_a = 0;
m_b = 0;
cout << "无参构造函数 " << m_a << " " << m_b << endl;
}
Test2(int a) {//有参数的构造函数
m_a = a;
m_b = 0;
cout << "有参构造函数 " << m_a << " " << m_b << endl;
}
Test2(int a, int b) {//有两个参数的构造函数
m_a = a;
m_b = b;
cout << "有参构造函数 " << m_a << " " << m_b << endl;
}
/*** 一个拷贝构造函数 */
Test2(const Test2 &obj) {//参数是引用类型
//拷贝构造函数用于,一个对象给另外一个对象赋值
m_a = obj.m_a;
m_b = obj.m_b;
cout << "拷贝构造函数 " << m_a << " " << m_b << endl;
}
//成员函数
void init(int a, int b) {
m_a = a;
m_b = b;
}
void print() {
cout << "print " << m_a << " " << m_b << endl;
}
int getA() {
return m_a;
}
~Test2() {
cout << "析构函数 " << m_a << " " << m_b << endl;
}
};
5.3.2 构造函数调用规则研究
- 示例
//有参构造函数的调用
void objTest2() {
//1. 括号法(C++编译器自动调用的构造函数)
Test2 t1(1);//c++编译器自动的调用对应的构造函数
Test2 t2(2, 3);
//2. 等号法,只用于调用单个参数的构造函数(编译器自动调用的构造函数)
Test2 t3 = (4, 5, 6, 7, 8);// C++对"=" 功能增强,这里调用了单个参数构造函数,取值以最后一个,的值为准
Test2 t4 = 9;//这里是不是把9赋值给t4;,不是的,这里调用了Test2的构造函数
//3. 直接调用构造函数 (手动调用的构造函数)
Test2 t5 = Test2(1, 2);
//==========================================================================================//
t1 = t5; //把 t5 copy给 t1 ,对象的赋值操作,这里不是调用构造函数(属于对象的赋值)
Test2 t6 = t5; //这里是调用了拷贝构造函数(属于对象的初始化)
Test2 t7(t5); //这里是调用了拷贝构造函数(属于对象的初始化)
}
- 输出
构造函数分类
有参构造函数 1 0
有参构造函数 2 3
有参构造函数 8 0
有参构造函数 9 0
有参构造函数 1 2
拷贝构造函数 1 2
拷贝构造函数 1 2
析构函数 1 2
析构函数 1 2
析构函数 1 2
析构函数 9 0
析构函数 8 0
析构函数 2 3
析构函数 1 2
==========================
5.3.3 拷贝函数的调用时机
- 示例
void copyTest(Test2 t) {
cout <<"m_a:"<< t.getA() << endl;
}
Test2 getTest2() {
Test2 t(4, 5);//是在栈上创建的对象t
return t;//这里返回栈上的t,但是该函数调用完成后,t会被释放
}
/**
* 拷贝构造函数的调用时机
*/
void objTest3() {
cout << "----拷贝构造函数的调用时机----" << endl;
Test2 t1(1, 2);
cout << "----t1初始化完毕----" << endl;
//1 用=初始化对象时候,会调用拷贝构造函数
Test2 t2 = t1;
t2.print();
cout << "----t2初始化完毕----" << endl;
//2 用()初始化对象的时候,会调用拷贝构造函数
Test2 t3(t2);
cout << "----t3初始化完毕----" << endl;
//3 当我们用实参(t2)初始化一个函数的形参t的时候,也会自动调用拷贝构造函数
copyTest(t2);//这里调用函数后,会调用Test2的析构函数
cout << "----copyTest()调用完毕----" << endl;
//getTest2();//这里调用函数后,会调用Test2的析构函数
//初始化t4, 创建一个匿名对象(getTest2()),把从匿名对象转成了有名字对象t4(扶正),
Test2 t4 = getTest2();//这里是等整个函数调用后才会调用析构函数
t4.print();
cout << "----t4初始化完毕----" << endl;
//重新给t1赋值
t1 = getTest2();//注意和上面的t4进行区别,上面是初始化
t1.print();
cout << "----t1赋值调用完毕----" << endl;//结束后会调用4次Test2的析构函数,因为这里创建了4个对象
}
- 输出
----拷贝构造函数的调用时机----
有参构造函数 1 2
----t1初始化完毕----
拷贝构造函数 1 2
print 1 2
----t2初始化完毕----
拷贝构造函数 1 2
----t3初始化完毕----
拷贝构造函数 1 2
m_a:1
析构函数 1 2
----copyTest()调用完毕----
有参构造函数 4 5
print 4 5
----t4初始化完毕----
有参构造函数 4 5
析构函数 4 5
print 4 5
----t1赋值调用完毕----
析构函数 4 5
析构函数 1 2
析构函数 1 2
析构函数 4 5
5.3.4 深拷贝和浅拷贝
C++中类的拷贝有两种:深拷贝,浅拷贝:当出现类的等号赋值时,即会调用拷贝函数
- 两个的区别
- 在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝,它能够完成成员的一一复制。当数据成员中没有指针时,浅拷贝是可行的;但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象,所以,此时,必须采用深拷贝。
- 深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝。
- 总结
深拷贝和浅拷贝的区别是在对象状态中包含其它对象的引用的时候,当拷贝一个对象时,如果需要拷贝这个对象引用的对象,则是深拷贝,否则是浅拷贝。
c++默认的拷贝构造函数是浅拷贝,浅拷贝就是对象的数据成员之间的简单赋值,如你设计了一个没有类而没有提供它的复制构造函数,当用该类的一个对象去给令一个对象赋值时所执行的过程就是浅拷贝,如:
class A
{
public:
A(int _data) : data(_data){}
A(){}
private:
int data;
};
int main()
{
A a(5), b = a; // 仅仅是数据成员之间的赋值
}
这一句b = a
;就是浅拷贝,执行完这句后b.data = 5
;如果对象中没有其他的资源(如:堆,文件,系统资源等),则深拷贝和浅拷贝没有什么区别,但当对象中有这些资源时,例子:
class A
{
public:
A(int _size) : size(_size)
{
data = new int[size];
} // 假如其中有一段动态分配的内存
A(){};
~A()
{
delete [] data;
} // 析构时释放资源
private:
int* data;
int size;
}
int main()
{
A a(5), b = a; // 注意这一句
}
这里的b = a
会造成未定义行为,因为类A中的复制构造函数是编译器生成的,所以b = a
执行的是一个浅拷贝过程。我说过浅拷贝是对象数据之间的简单赋值,比如:
b.size = a.size;
b.data = a.data; // Oops!
这里b的指针data和a的指针指向了堆上的同一块内存,a和b析构时,b先把其data指向的动态分配的内存释放了一次,而后a析构时又将这块已经被释放过的内存再释放一次。对同一块动态内存执行2次以上释放的结果是未定义的,所以这将导致内存泄露或程序崩溃。所以这里就需要深拷贝来解决这个问题,深拷贝指的就是当拷贝对象中有对其他资源(如堆、文件、系统等)的引用时(引用可以是指针或引用)时,对象的另开辟一块新的资源,而不再对拷贝对象中有对其他资源的引用的指针或引用进行单纯的赋值。如:
class A
{
public:
A(int _size) : size(_size)
{
data = new int[size];
} // 假如其中有一段动态分配的内存
A(){};
A(const A& _A) : size(_A.size)
{
data = new int[size];
} // 深拷贝
~A()
{
delete [] data;
} // 析构时释放资源
private:
int* data;
int size;
}
int main()
{
A a(5), b = a; // 这次就没问题了
}
5.3.5 多个对象构造和析构
- 对象初始化列表
class A {
public:
A(int a) {
m_a = a;
cout << "构造函数: " << m_a << endl;
}
~A() {
cout << "析构函数" << endl;
}
int getMA() {
return m_a;
}
private:
int m_a;
};
/*
* 构造函数的初始化列表
* 语法:ClassName():m1(xxx),m2(xxx),m3(xxx)
* 1. 为了在B类中组合了一个A类对象(A类有有参数的构造函数)
* 2. 初始化const type 常变量,必须用初始化列表
*/
class B {
private:
//初始化列表里面初始化的顺序是按照我们成员变量声明的顺序,不是初始化列表的顺序
int m_b;//在初始化列表里面 m_a(1),m_b(2) 这个是谁先初始化
A m_a;
A m_a2;//编译器它不知道要调用哪个构造函数
A m_a1;
const int c;//常变量java final
public:
// B(int b,A a):m_a(2),m_b(12)//构造函数的初始化列表 默认值
// m_b = b;
// m_a = a;
// }
B() : m_a2(3), m_a1(2), m_a(m_a2), m_b(10), c(3) {
//初始化列表一定要注意顺序问题,是一种错误的习惯
}
B(int b) : m_a(2), m_a2(3), m_a1(4), c(5) {
//正确的初始化列表顺序应该和声明的变量的顺序是一致的
}
void print() {
cout << m_a.getMA() << " " << m_b << endl;
}
};
- 调用
//我要初始化B的b1的时候,那么我必须先初始化B类的成员变量A类型 m_a
//默认构造函数
/*
* 1. 默认的无参构造函数,当我们的类没有定义构造函数时,C++编译器默认会提供一个无参构造函数,函数体是空实现
* 2. 当我们自己写了构造函数之后,系统不会给我们提供默认无参的构造函数了
* 3. 默认拷贝构造函数
* 4. 只要你写了构造函数,C++编译器就不会提供默认的
*/
int main01() {
//TODO: 多个对象构造和析构
cout << "多个对象构造和析构" << endl;
// int b(12);
//
// bool bb(true);
//
// float f(3.14);
// float f1 = 1.2;
//
// cout << "b: " << b << " bb: " << bb << " f: " << f << endl;
//java 自动装箱 自动拆箱
// B b1(4, A(45));
// b1.print();
//初始化列表顺序研究
B b2;
return 0;
}
5.4 构造函数和析构函数的调用顺序研究
//C++中构造函数再去调用构造函数是一个危险的行为
class Test {
private:
int a;
int b;
int c;//int 默认值是0,但是不要相信编译器给的默认值
public:
Test(int a, int b, int c) {
this->a = a;
this->b = b;
this->c = c;
}
Test(int a, int b) {
this->a = a;
this->b = b;
Test(a, b, 10);
//构造函数里面调用另外一个构造函数 新产生一个匿名对象C,这里面调用后就会被析构了.
//因此C++中不要再构造函数再去调用构造函数
}
void print() {
cout << a << " " << b << " " << c << endl;
}
~Test() {
cout << "析构函数" << endl;
}
};
- 调用:
//如何在C++里面正确的初始化一个对象
int main02() {
//TODO: 构造函数研究
cout << "构造函数研究" << endl;
//在栈上面创建对象和释放对象
Test t1(1, 2);
t1.print();
return 0;
- 结果输出:
/Users/mac/Project/C/Practice_C/cmake-build-debug/Practice_C
构造函数研究
析构函数
1 2 0
析构函数
Process finished with exit code 0
5.5 对象的动态建立和释放
- new和delete基本语法: 用于对象的动态创建和释放
// new delete /new[] delete[]
// 对象的动态创建和释放
// 在栈上创建对象 和释放对象 与 在堆上创建对象和释放对象 的区别
// 栈(栈的大小有限)
// 1. 在栈上创建的对象,一经创建,对象的大小是无法改变的
// 2. 在栈上的对象 系统自动创建和销毁
// 堆(创建复杂类型时)
// 1. 堆上申请的内存空间 是可以动态调整的
// 2. 堆上的申请的空间,必须自己申请与释放
//堆上创建对象和释放对象方式
// malloc/calloc free (C语言中的函数)
// new delete (C++的语法 new delete 它们是属于运算符 不是函数, 如: sizeof(int);//这也是运算符)
//main函数是在栈上执行的
int main03() {
//TODO: new delete基本语法
cout << "new delete基本语法" << endl;
//使用malloc函数在堆上申请了一块内存空间
int *p = (int *) malloc(sizeof(int));//在堆上申请了一块内存空间
*p = 10;
//释放堆上的内存
free(p);
p = nullptr;
//使用new运算符在堆上申请了一块内存空间
int *p1 = new int;//在堆上申请内存 分配基础类型
*p1 = 20; cout << "*p1: " << *p1 << endl;
//释放new的内存
delete p1;
cout << "*p1: " << *p1 << endl;//这里p1释放后还是20,释放内存,不代表清空p1的值,只是当前的内存不再受保护
//使用new运算符在堆上申请了一块内存空间
int *p2 = new int(30);
cout << "*p2: " << *p2 << endl;
delete (p2);
cout << "*p1: " << *p1 << endl;//这里p1变成30了,说明之前释放的内存被p2用了
return 0;
}
- 输出结果:
/Users/mac/Project/C/Practice_C/cmake-build-debug/Practice_C
new delete基本语法
*p1: 20
*p1: 20
*p2: 30
*p1: 30
Process finished with exit code 0
- 在堆上申请数组
int main04() {
//TODO: 在堆上分配数组
cout << "在堆上分配数组" << endl;
// C中方式
int *array = (int *) malloc(sizeof(int) * 10); // int array[10]
array[0] = 20;
free(array);//释放内存
//C++中方式
int *array1 = new int[10];//int array[10] C++的方式在堆上申请了一个int[10]的数组
array1[2] = 3;//赋值
// delete array1; //对吗?
delete[] array1;//这才是正取的释放数组
// new/delete 与new[]/delete[] 是两组不同的运算符
return 0;
}
- 在堆上分配复杂类型对象
int main05() {
//TODO: 在堆上分配复杂类型对象
cout << "在堆上分配复杂类型对象" << endl;
//c中方式
Test1 *pt1 = (Test1 *) malloc(sizeof(Test1));
pt1->print();//调用方法不会自动调用构造函数
free(pt1);//释放时候也不会自动调用析构函数
cout << "================" << endl;
//c++方式
Test1 *pt2 = new Test1(10);//会自动调用构造函数
pt2->print();
delete pt2;//会自动调用析构函数
return 0;
}
- 输出
/Users/mac/Project/C/Practice_C/cmake-build-debug/Practice_C
在堆上分配复杂类型对象
0
================
构造函数 10
10
析构函数 10
Process finished with exit code 0
6 静态成员变量和成员函数
using namespace std;//使用命名空间std标准的命名空间
//static
class Test {
public:
//成员函数
void printC() {
cout << "c: " << c << endl;
getC();//成员函数 可以使用静态变量 或者静态函数
}
void addC() {
c = c + 1;
}
//非私有静态成员变量初始化
static int d;
//静态成员函数
static int getC() {
//静态成员函数 不能调用普通成员函数 或者普通成员变量
cout << "c: " << c << endl;
// cout << "a: " << a<<endl;
return c;
}
private:
int a;
int b;//成员变量是 是每个对象 都有一份
static int c;//静态成员变量 类的多个对象共享
};
//2.私有静态成员变量初始化(方法外部)
int Test::c = 1;
- 调用
int main01() {
//TODO: static
cout << "static" << endl;
//1.非私有静态成员变量初始化
// Test::d = 10;
// int Test::d = 1;//这里不能这么写
//静态成员函数调用
Test t1;
//方式一:
t1.getC();
//方式二:
Test::getC();
return 0;
}
- 输出
/Users/mac/Project/C/Practice_C/cmake-build-debug/Practice_C
static
c: 1
c: 1
Process finished with exit code 0
7 友元
- 友元函数
class Test1 {
public:
//在类内部定义友元类(另一个类)的关联
friend class Test2;//友元类 Test2是Test1的好朋友,
//在Test2中可以访问Test1类的私有的成员变量,私有函数(private/protected)
//无参构造
Test1() {
}
//有参构造
Test1(int a, int b) {
this->a = a;//构造函数内给私有成员变量赋值
this->b = b;//构造函数内给私有成员变量赋值
}
//成员方法
void add() {
a = a + b;
cout << "a: " << a << endl;
}
int c;
private:
//内部定义友元函数的关联函数(无函数体)
friend void add1(Test1 t);//友元函数的声明方式,关键字 friend
friend void print1();
int a;
int b;
static int d;
void print() {
cout << "a: " << a << " ,b: " << b << " ,c: " << c << endl;
}
};
//在类的外部类里面的私有的成员变量()
//1. 友元函数是没有this指针的
//2. 你要访问的是类的非静态成员,需要对象做参数
//2. 你要访问的是类的静态成员,则不需要对象做参数
//3. 如果做参数的是全局对象,则不需要对象做参数
//为什么要设计友元函数 友元类
//1. 开了一个后门
void add1(Test1 t) {
//你要访问的是类的非静态成员,需要对象做参数
t.a = t.a + t.b;
//你要访问的是类的静态成员,则不需要对象做参数
Test1::d;
cout << "a: " << t.a << t.c << endl;
}
void print1() {
// cout <<"gTest.a: " << gTest.a << Test1::d<<endl;
}
- 友元类
/**
* 友元类
*/
class Test2 {
public:
void setA(int a) {
//友元类可以访问声明类的私有
t1.c = a;
t1.a = a;
}
void print() {
cout << "t1.a: " << t1.a << endl;
t1.print();
}
private:
Test1 t1;
};
- 调用
//类的友元函数
//1. 定义在类的外部
//2. 有权访问类的所有private/protected的成员
int main02() {
//TODO: 友元函数 友元类
cout << "友元函数 友元类" << endl;// << >> 原本的一样:位移 流的输入输出
Test1 t1;
// Test1::d = 10;
Test2 t2;
t2.setA(2);
t2.print();
return 0;
}
- 输出
/Users/mac/Project/C/Practice_C/cmake-build-debug/Practice_C
友元函数 友元类
t1.a: 2
a: 2 ,b: 0 ,c: 2
Process finished with exit code 0
8 运算符重载
- 什么是运算符重载
// 运算符重载
// 可以使得用户自定义的数据,使用起来更简单
/*
* java String "abc" + "def"
* c/c++ strcat
*/
/**
* 以复数为例,演示运算符重载
*/
class Complex {
public:
Complex(int a = 0, int i = 0) {
this->a = a;
this->i = i;
cout << "有参构造函数" << a << " " << i << endl;
}
~Complex() {
cout << "析构函数" << a << " " << i << endl;
}
void print() {
cout << "Complex: (" << a << "+" << i << "i)" << endl;
}
//所以的运算符 C++编译器是不是已经给我们实现了一套基础数据类型的
//- 重载为成员函数 二元运算符的重载(main01_2)
Complex operator-(Complex &c) {
Complex tmp(this->a - c.a, this->i - c.i);
return tmp;
}
//前置-- 重载为成员函数 一元运算符的重载(main01_4)
Complex &operator--() {
//没有用占位参数的 前置(这里是重载为成员函数,并且是自操作的前置一元运算符,因此不需要参数)
this->a--;
this->i--;
return *this;//返回本身
}
//后置-- 重载为成员函数 一元运算符的重载(main02)
Complex operator--(int) {//有占位参数的 就是后置的
Complex tmp = *this;
// return c;
this->a--;
this->i--;
return tmp;//返回一个元素
}
private:
friend Complex myAdd(Complex c1, Complex c2);
//+ 重载为友元函数 二元运算符的重载(main01_1)
friend Complex operator+(Complex c1, Complex c2);
//前置++ 重载为友元函数 二元运算符的重载(main01_3)
friend Complex &operator++(Complex &c);
//后置++ 二元运算符的重载(main02)
friend Complex operator++(Complex &c, int);
//<< 重载为友元函数
friend ostream &operator<<(ostream &out, Complex &c);
int a;
int i;
};
//+ 重载为友元函数 二元运算符的重载(main01_1)
Complex operator+(Complex c1, Complex c2) {//看成是一个函数
Complex tmp(c1.a + c2.a, c1.i + c2.i);
return tmp;
}
//前置++ 重载为友元函数 一元运算符的重载
Complex &operator++(Complex &c) {
//这里只需要一个参数
c.a++;
c.i++;
return c;//返回本身
}
//后置++
Complex operator++(Complex &c, int) {//占位参数(兼容C不规范的写法和这里会用到)
// 后置
// 1. 返回Complex变量本身
// 2. +1 -1
Complex tmp = c; //后置++和--这里如果返回局部(函数栈)的引用/指针都是不可以的,可能报错, (这里用临时变量实现先返回在操作的后置++效果)
c.a++;
c.i++;
return tmp;//返回一个元素
}
//运算符重载分类
/*
* //按照实现方式
* 1. 运算符重载为成员函数
* 2. 运算符重载为全局函数(友元)
*
* //按运算符需要的操作数
* 1. 一元运算符: 如 ++ -- (包括前置/后置)
* 2. 二元运算符: 如 数学运算符(+ - * /)
* 形式: ObjectL op ObjectR
* 2.1 重载为成员函数
* ObjectL.operator op(ObjectR)
* 2.2 重载为友元函数
* operator op(ObjectL,ObjectR)
*/
//重载为友元函数 out. << Complex 不能使用成员函数的形式重载,因为ostream是源码里面的,无法为其添加成员函数
//当我们无法修改左操作数的类时,只能使用友元函数
/*
void operator<<(ostream &out,Complex &c){
out <<"Complex: ("<<c.a<<"+"<<c.i<<"i)"<<endl;
}
*/
ostream &operator<<(ostream &out, Complex &c) {
out << "Complex: (" << c.a << "+" << c.i << "i)" << endl;
return out;//返回自身,使其
}
/**
* 类的外部实现复杂类型的激发
* @param c1
* @param c2
* @return
*/
Complex myAdd(Complex c1, Complex c2) {
Complex tmp(c1.a + c2.a, c1.i + c2.i);
return tmp;
}
int main02() {
cout << "=========================复杂类型的前置自加和自减(一元运算符)->运算符重载实现->重载为友元函数和成员函数===================" << endl;
//前置
Complex c1(3, 3);
c1.print();
++c1;
c1.print();
--c1;
c1.print();
// ++c5;//我们需要去重载++ 支持Complex
//1. c5 +1
//2. 返回c5本身
// 后置
// 1. 返回Complex变量本身
// 2. +1 -1
// Complex& operator ++(Complex &c);
cout << "=========================复杂类型的后置自加(一元运算符)->运算符重载实现->重载为友元函数===================" << endl;
Complex c2(6, 6);
c2.print();
c2++; // -> 首先推断出友元 Complex operator ++(Complex &c)
c2.print();
cout << "=========================复杂类型的后置自减(一元运算符)->运算符重载实现->重载为成员函数===================" << endl;
c2--;
c2.print();
return 0;
}
int main01() {
//TODO: 运算符重载
//基础数据类型 如何运算 C++编译器是不是已经定义好了的
// int a = 0;
// int b = 0;
// int c;
// c = a + b;
cout << "====================运算符重载=====================" << endl;
//字符数组运算
//"abc " + "def"
//复数 (实数 + 虚数 = 复数)
//复数加法计算: a + i = (a1+i1) + (a2+i2) = (a1+a2) + (i1+i2)
Complex c1(1, 2);
c1.print();
Complex c2(3, 4);
cout << "=================复杂类型的加法(二元运算符)->外部方法实现=====================" << endl;
//复杂类型的加法-外部方法实现
Complex sum;// sum c1 + c2;C++编译器如何支持操作符重载的?
sum = myAdd(c1, c2);
sum.print();
cout << "=================复杂类型的加法(二元运算符)->运算符重载实现->重载为友元函数=================" << endl;
//复杂类型的加法-运算符重载实现
sum = c1 + c2;// 函数调用
sum.print();
//不是所有的运算符都可以重载
/*不可以重载的运算符
* 1 . 成员访问运算符
* 2 .*, ->* 成员指针访问运算符
* 3 :: 域运算符
* 4 sizeof 长度运算符
* 5 ?: 三目运算符
* 6 # 预处理符
*/
// new/delet new[] delete[] 这两组运算符也是可以重载的
//运算符重载实现的步骤:
// sum c1 + c2
// 1. 把操作符重载 认为是一个函数调用 -> operator+(看成函数名) -> 推断出函数原型(c1.operator +(c2);)
// 2. 分析函数参数 根据左右操作数的个数 -> operator + (ClassName &classname)
// 3. 分析函数的返回值 友元形式:Complex operator+(Complex &c);
cout << "=====================复杂类型的减法(二元运算符)->运算符重载实现->重载为成员函数=======================" << endl;
// objTest();
Complex c3(3, 4), c4(1, 2);//这里会调用有参构造 (打印出: 有参构造函数3 4 有参构造函数1 2)
Complex result;//这里初始化 (打印出: 有参构造函数0 0)
result = (c3 - c4);//这里赋值,是一个扶正的过程(这里才会将result初始化) (打印出: 有参构造函数2 2)
cout << "=========================复杂类型的前置自加(一元运算符)->运算符重载实现->重载为友元函数===================" << endl;
Complex c5(3, 3);
// ++c5;//自己定义的类,我们需要去重载++运算符 支持Complex (基础类型的数据 C++编译器已经给我们实现了一套一元运算符的重载,所以可以直接调用)
//1. c5 +1
//2. 返回c5本身
// Complex& operator ++(Complex &c);
++c5;
c5.print();
cout << "=========================复杂类型的前置自减(一元运算符)->运算符重载实现->重载为成员函数===================" << endl;
--c5;
c5.print();
return 0;
}
- 运算符重载-只能通过友元函数重载的
int main03() {
//TODO: 运算符重载-只能通过友元函数重载的
//1. 只能用友元函数
//当我们无法修改左操作数的类时,只能使用友元函数
cout << "==============只能通过友元函数重载的运算符1============== " << endl;
Complex c1(1, 2);
cout << "sdfdfasdf " << endl;
cout << "Compex: " << c1 << endl;
cout << "==============只能通过友元函数重载的运算符2============== " << endl;
cout << c1 << "sdfdfasdf " << endl;
cout << "==============只能通过友元函数重载的运算符3============== " << endl;
cout << ("Compex:");
// cout<<(Complex& c);
// void operator<<(ostream,Complex);
return 0;
}
/Users/mac/Project/C/Practice_C/cmake-build-debug/Practice_C
==============只能通过友元函数重载的运算符1==============
有参构造函数1 2
sdfdfasdf
Compex: Complex: (1+2i)
==============只能通过友元函数重载的运算符2==============
Complex: (1+2i)
sdfdfasdf
==============只能通过友元函数重载的运算符3==============
Compex:析构函数1 2
Process finished with exit code 0
- 运算符重载-只能通过成员函数重载的
/**
* 运算符重载-只能通过成员函数重载(=)
* 模拟String类
*/
class Name {
public:
Name(char *pName) {
size = strlen(pName);
this->pName = (char *) malloc(size + 1);
strcpy(this->pName, pName);
}
Name(const Name &name) {
size = name.size;
this->pName = (char *) malloc(size + 1);
strcpy(this->pName, name.pName);
}
~Name() {
if (pName != NULL) {
free(pName);
pName = NULL;
size = 0;
}
}
//通过成员函数重载
Name &operator=(Name &obj) {
//本身对象存在时,先释放本身
if (this->pName != NULL) {
delete[] pName;
size = 0;
}
//赋值
size = obj.size;
//创建空间并拷贝内容
pName = new char[size + 1];
strcpy(pName, obj.pName);
return *this;
}
protected:
char *pName;//相当于 char pName[](字符数组)
int size;
};
int main04() {
//TODO: 运算符重载-只能通过成员函数重载的
//2. 只能通过成员函数重载
// = [] () -> 等操作符 只能通过成员函数进行重载
cout << "==========================只能通过成员函数重载的=========================" << endl;
Name obj1("asdfsdfw");
Name obj2 = obj1;//默认copy构造函数 浅拷贝,会导致问题
//重载= ,
// Name& operator = (Name& obj)
cout << "main结束" << endl;
return 0;
}
- new运算符 重载
/**
* new运算符 重载
*/
class Test {
public:
Test() {
cout << "无参构造" << endl;
}
Test(int i) {
cout << "有参构造" << i << endl;
}
//成员函数方式重载 new
void *operator new(size_t size) {
cout << "new" << endl;
void *p = malloc(size);//new 相当于开辟内存空间
return p;
}
};
int main05() {
cout << "=======================new运算符重载==============" << endl;
//实现在new 的时候,进行其他的操作(如先打印一串数据),这时候就可以通过重载new 操作符实现
Test *t = new Test;
return 0;
}
9 继承和多态
9.1 继承
//java 继承 extends
//C++ OOP
// class Child : Parent{} //默认是private
// class Child : [权限访问] private|protected|public Parent{}
// 访问控制权限 子类(派生类)可以访问父类(基类)中的所有的非私有成员
//作业2 成员 public protected private 继承关系 public protected private 做成一个表格
//成员修饰 public protected private
//继承关系 public
// protected
// private
class Parent {
public:
int a;// 名字
void print() {
cout << "a: " << a << " b: " << b << " c: " << c << endl;
}
protected:
int b;//银行密码
private:
int c;//老婆
};
//class关键字默认继承关系是private
class Child : Parent {
//默认
void test() {
this->b = 1;
this->a = 2;
// this->c = 3;
}
};
//私有继承 private
class Child1 : private Parent {
void test() {
this->b = 1;
this->a = 2;
// this->c = 3;
}
};
//保护继承 protected
class Child2 : protected Parent {
public:
void test() {
this->b = 1;
this->a = 2;
// this->c = 3;
}
};
//公有继承 public
class Child3 : public Parent {
public:
void test() {
this->b = 1;
this->a = 2;
// this->c = 3;
}
};
//结构体定义类
struct Base {
int a;
int b;
int c;
};
//struct关键字继承 默认是public(注意和class定义类时默认继承方式的区别)
struct B1 : Base {
};
/*
* public 修饰的成员变量 函数 在类的内部,类的外部都能使用
* protected 修饰的成员变量 函数 在类的内部使用,在继承的子类中可用,其他 类的外部都不能使用
* private 修饰的成员变量 函数 在类的内部使用,其他 情况下都不能使用
*/
//派生类访问控制
//1 protected 修饰的成员 是为了在家族中使用,为了继承
//在实际的项目中 全部用 public
// 我们需要知道 面试
int main03() {
//TODO: 继承
cout << "继承" << endl;
Child child;
// child.a = 10; //error
// child.b = 20; //error
// child.c = 30; //error
// 外部可见性 private继承: 父类public protected private -> 不可见
Child1 child1;
// child1.a = 10; //error
// child1.b = 20; //error
// child1.c = 30; //error
// 外部可见性 protected继承: 父类public protected private -> 不可见
Child2 child2;
// child2.a = 10; //error
// child2.b = 20; //error
// child2.c = 30; //error
// 外部可见性 public继承: 父类public-> 可见; protected private -> 不可见
Child3 child3;
child3.a = 10; //ok
// child3.b = 20; //error
// child3.c = 30; //error
B1 b1;
b1.a = 1;
b1.b = 2;
b1.c = 3;
return 0;
}
//C++ 类型的兼容性问题
class parent {
public:
parent() {
// cout<<"构造 我是爸爸..."<<endl;
}
parent(const parent &p) {
// cout<<"copy构造 我是爸爸..."<<endl;
}
~parent() {
// cout<<"析构 我是爸爸..."<<endl;
}
//virtual 父类 多态关键字
virtual void print() { //虚函数 多态 抛个砖块....
cout << "我是爸爸: " << a << endl;
}
private:
int a;
};
//public形式继承
class child : public parent {
public:
void print() {
cout << "我是儿子: " << c << endl;
}
void print1() {
cout << "我是儿子: " << c << endl;
}
private:
int c;
};
/**
* 指针为函数参数
* @param base
*/
void testPrint(parent *base) {
base->print();
}
/**
* 引用作为函数参数
* @param base
*/
void testPrint(parent &base) {
base.print();
}
int main04() {
//TODO: 兼容性问题
cout << "兼容性问题" << endl;
parent p1;
// p1.print();
child c1;
// c1.print();//调用父类
// c1.print1();//调用子类
//兼容性问题:
//使用基类指针(引用) 指向子类对象 Parent parent =new Child()
//java面向接口编程
parent *p = nullptr;
p = &c1;
//print()是调用那个 C++编译器调用时候,是根据指针的类型来判断
p->print();//我是儿子: 0 ,如果子类不重写print方法,
// java中呈现成多态
//指针作为函数参数(无论传入父类还是子类的对象都会调用父类的方法) ; 如果父类是虚函数 子类重写后,传入子类的对象会调用子类的方法
testPrint(&p1); //我是爸爸: 0
testPrint(&c1); //我是爸爸: 0
//引用作为函数参数(无论传入父类还是子类的对象都会调用父类的方法) ; 如果父类是虚函数 子类重写后,传入子类的对象会调用子类的方法
testPrint(p1); //我是爸爸: 0
testPrint(c1); //我是爸爸: 0
// 用子类对象 初始化父类对象
parent p2 = c1;//ok 向上转型
// child c2 = p1;//error 向下转型 在特定的语义环境下可以强转
return 0;
}
//继承的构造与析构
class Base1 {
public:
Base1(int a = 0, int b = 0) {
this->a = a;
this->b = b;
cout << "父类构造函数" << endl;
}
~Base1() {
this->b = b;
cout << "父类析构函数" << endl;
}
void print() {
cout << "父类: " << a << " " << b << endl;
}
private:
int a;
int b;
};
class A {
public:
A(int i) {
this->i = i;
cout << "A构造函数" << endl;
}
~A() {
cout << "A析构函数" << endl;
}
private:
int i;
};
/**
* B2是Base1的子类
*/
class B2 : public Base1 {
public:
//子类通过初始化列表的形式,调用父类的构造函数(初始化父类的成员变量)
B2(int a = 0, int b = 0, int c = 0) : Base1(a, b),a(4) {
//父类先初始化 还是子类的成员变量先初始化 ?
this->c = c;
cout << "子类构造: " << c << endl;
}
//初始化列表的使用情况
// 1. 成员变量是一个类类型,类还是 有参数的构造函数
// 2. const 变量
// 3. 初始化父类的成员变量
//下面这种形式是Java中调用父类的构造函数方式,在C++中是不正确的
// B2(int a= 0,int b= 0,int c = 0){
// Base1(a,b);
// this->c = c;
// cout<<"子类构造: "<< c<<endl;
// }
~B2() {
cout << "子类析构: " << c << endl;
}
private:
int c;
A a;
};
void testObj() {
B2 b2(1, 2, 3);
b2.print();
}
int main05() {
//TODO: 继承中的构造析构问题
cout << "继承中的构造析构问题" << endl;
testObj();
return 0;
}
int main() {
// main03();
// main04();
main05();
}
9.2 多态
//=======================================================================================================================//
// 多态
class Parent{
public:
Parent(int a = 0):a(a){}
virtual void print(){
cout <<"parent 1"<<endl;
}
void print2(){
cout <<"parent print2 2"<<endl;
}
private:
int a = 0;
};
//virtual加载类上面主要解决环形继承问题
class D: virtual public Parent{
public:
D(int a = 0,int b = 0):Parent(a),b(b){}
//只有虚函数才能产生多态
void print(){
cout <<"child 1"<<endl;
}
void print2(){
cout <<"child print2 2"<<endl;
}
private:
int b= 0;
};
void objTest1(Parent *parent){
parent->print();//print实现了多态
parent->print2();//print2未实现,调用的始终是父类的方法
}
int main06(){
Parent p1;//定义父类
D child1;//定义子类
//这里想要传入父类对象调用父类方法,传入子类对象调用子类方法->即多态效果
objTest1(&p1);
objTest1(&child1);
return 0;
}
10 抽象类
//C++的抽象类
// java中 abstract
class Shape{
//抽象类,这个抽象类是无法实例化的
public:
virtual void area() =0; // =0可看作为关键字(该类即为抽象类); 该函数变为纯虚函数
void print(){
cout << "Shape"<<endl;
}
virtual void func(){
}
};
class Circle:public Shape{
public:
Circle(int r):r(r){}
virtual void area(){
cout <<"Circle area: " << 3.14 * r *r << endl;
}
//虚函数重写形成多态
void func(){
}
private:
int r;
};
//概念: 重写 重定义 重载
//重写: 发生在两个类之间, 如果是虚函数重写,就会发生多态
//1 虚函数 多态
//2 非虚函数 重定义
//重载:
//必须是发生在同一个类之间
class Tri:public Shape{
public:
Tri(int a,int h):a(a),h(h){}
virtual void area(){
cout <<"Tri area: " << a * h /2 << endl;
}
//非虚函数重写->重定义
void print(){
cout << "Tri"<<endl;
}
//print()函数的重载
void print(int a){
cout <<"a: " << a <<endl;
}
private:
int a;
int h;
};
void getArea(Shape* shape){
shape->area();
shape->print();
}
int main07(){
//面向接口 抽象编程
// Shape * base = new Shape; 抽象类无法直接初始化
Shape *base = nullptr;//定义抽象类
Circle c1(10);
Tri tr(3,6);
base = &c1;//base 指向 c1
base->area();//多态 //Circle area: 314
getArea(&c1);//Circle area: 314 ; Shape
getArea(&tr);//Tri area: 9 ; _____3
tr.print();//Tri
return 0;
}
11 类型转换
//C++ 类型转化
// (T)expre C 风格
// T(expre) 函数风格
/*
* static_cast<T>(expre)
* dynamic_cast<T>(expre)
* const_cast<T>(expre)
* reinterpret_cast<T>(expre)
*/
/**
* static_cast<T>(expre)
* @return
*/
int main08(){
//double转int
double d = 3.14;
int a = (int)d;
int b = int(d);
int b1 = static_cast<int>(d);//C++风格
//int转const int
int a1 = 3;
const int b2 = (const int)a1;//c风格
const int c1 = static_cast<const int>(a1);//C++风格
return 0;
}
class B1{
public:
void func(){};
virtual void func1(){};
};
class D1:public B1{};
/**
* dynamic_cast<T>(expre)
* 动态转换
* @return
*/
int main09(){
D1 *d = new D1();
B1 *b1 = new B1();
//向上转型
B1 *b12 = dynamic_cast<B1*>(d);
cout<<"b12: " <<b12<<endl;
//向下转型-> 基类必须是有虚函数才支持
D1 *d1 = dynamic_cast<D1*>(b1);
cout<<"d1: " <<d1<<endl;//结果是0 即:NULL
return 0;
}
void func(int *i){}
/**
* const_cast<T>(expre)
* 常量型
* @return
*/
int main10(){
const int a = 10;
// func(&a);//error
func(const_cast<int *>(&a));//OK
return 0;
}
/**
* reinterpret_cast<T>(expre)
* 转换指针
* @return
*/
int main11(){
//转化指针的,把指针转化为任何类型的指针
int a = 0x76;//ASCII v
printf("a = %d,addr=%p\n",a,&a);
int* ap = &a;
char* c = reinterpret_cast<char*>(ap);
printf("c=%c,addr=%p\n",*c,c);
return 0;
}
12 异常
//C++的异常
class F{
public:
~F(){
cout <<"~F"<<endl;
// throw 1;//析构函数不要抛出异常
cout <<"~F end"<<endl;
}
};
void f1()noexcept(false){
cout <<"f1 start"<<endl;
int a;
throw 2;
cout <<"f1 end"<<endl;
}
void f2()noexcept(false){
cout <<"f2 start"<<endl;
F f;
f1();//调了F1未捕获异常,需要抛出
cout <<"f2 end"<<endl;
}
void f3(){
cout <<"f3 start"<<endl;
try{
f2();//捕获异常
}catch(int i){
cout << "exception: " << i<<endl;
}
cout <<"f3 end"<<endl;
}
int main12(){
f3();
return 0;
}
13 模板函数
//C++模板
template <typename T>
//模板函数
void swap1(T& a,T& b){
T temp(a);
a = b;
b = temp;
}
//模板类
class Printer{
public:
template <typename T>
void print(const T& t){
cout <<t <<endl;
}
};
int main13() {
int i = 1;
int j = 2;
swap1(i,j);
cout <<"i: " << i <<" j: " <<j<<endl;
double n = 1.0;
double m = 2.0;
swap1(n,m);
Printer p;
p.print(i);
p.print(n);
return 0;
}
网友评论