C++学习笔记——类和对象

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

1.面向对象的三大特性封装、继承、多态

2.对象有其属性和行为

3.具有相同性质的对象可被抽象为类

1.封装

1.封装是C++面向对象三大特性之一

2.封装的意义

1将属性变量和行为函数作为一个整体表现生活中的事物

2将属性和行为加以权限控制公共public私有private保护protected

1.1.封装的意义

​1.在设计类的时候属性和行为写在一起表现事物

语法

class 类名{ 访问权限 属性 / 行为 };

可以通过成员方法对成员属性进行赋值调用成员方法→传入形式参数→赋值成员属性

//学生类
class Student {
public:
    void setName(string name) {
        m_name = name;
    }
    void setID(int id) {
        m_id = id;
    }

    void showStudent() {
        cout << "name:" << m_name << " ID:" << m_id << endl;
    }
public:
    string m_name;
    int m_id;
};

int main() {

    Student stu;
    stu.setName("德玛西亚");
    stu.setID(250);
    stu.showStudent();

    system("pause");

    return 0;
}

2.类在设计时可以把属性和行为放在不同的权限下加以控制作用域

访问权限有三种例如class的括号内称为类内main函数中对类成员属性进行修改称为类外

1public 公共权限类内可以访问类外可以访问

2protected 保护权限类内可以访问类外不可以访问

3private 私有权限类内可以访问类外不可以访问

4protected和private的区别体现在继承中子类虽然能够继承父类的private和protected的成员变量但子类可以访问父类的保护内容而不可以访问父类的私有内容

//三种权限
//公共权限  public     类内可以访问  类外可以访问
//保护权限  protected  类内可以访问  类外不可以访问
//私有权限  private    类内可以访问  类外不可以访问

class Person
{
    //姓名  公共权限
public:
    string m_Name;

    //汽车  保护权限
protected:
    string m_Car;

    //银行卡密码  私有权限
private:
    int m_Password;

public:
    void func()
    {
        m_Name = "张三";
        m_Car = "拖拉机";
        m_Password = 123456;
    }
};

int main() {

    Person p;
    p.m_Name = "李四";
    //p.m_Car = "奔驰";  //保护权限类外访问不到
    //p.m_Password = 123; //私有权限类外访问不到

    system("pause");

    return 0;
}

1.2.struct和class的区别

1.在C++中 struct和class唯一的区别就在于默认的访问权限不同

1struct默认权限为public

2class默认权限为private

2..尽管结构体可以包含成员函数但它们很少这样做。所以通常情况下结构体声明只会声明成员变量

3..声明结构体的方式和声明类的方式大致相同其区别如下

1使用关键字 struct 而不是关键字 class

2结构体声明通常不包括 public 或 private 的访问修饰符

class C1
{
    int  m_A; //默认是私有权限
};

struct C2
{
    int m_A;  //默认是公共权限
};

int main() {

    C1 c1;
    //c1.m_A = 10; //错误访问权限是私有

    C2 c2;
    c2.m_A = 10; //正确访问权限是公共

    system("pause");

    return 0;
}

1.3.成员属性设置为私有

优点1将所有成员属性设置为私有可以自己控制读写权限通过设置和调用对应的成员方法的方式无权限即不提供相应成员方法

优点2对于写权限我们可以检测数据的有效性

在设置成员属性时可以在成员方法中通过if判断输入的参数是否符合标准

通过公开的成员方法设置私有的成员属性因为私有属性默认只有类内可以访问但可以通过调用函数类外实现访问

class Person {
public:

    //姓名设置可读可写
    void setName(string name) {
        m_Name = name;
    }
    string getName()
    {
        return m_Name;
    }


    //获取年龄 
    int getAge() {
        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; //只写  情人
};


int main() {

    Person p;
    //姓名设置
    p.setName("张三");
    cout << "姓名 " << p.getName() << endl;

    //年龄设置
    p.setAge(50);
    cout << "年龄 " << p.getAge() << endl;

    //情人设置
    p.setLover("苍井");
    //cout << "情人 " << p.m_Lover << endl;  //只写属性不可以读取

    system("pause");

    return 0;
}

1.4.练习案例

1.对比两个类的成员属性是否相等

1通过全局函数判断需要传入两个参数两个类

2通过成员方法判断以一个类为基准仅需传入一个参数另一个类并且关于成员属性的使用需要注意传入的类需要加上前缀例如p.m_H而基准的类不需要m_H

2.当成员属性为私有时通过成员方法设置和访问成员属性

3.在类中可以让另一个类作为本类的成员Circle类中包含Point类

4.类可以拆分在不同的文件中以点类为例若是圆类因其包含有点类故需要加上#include "point.h"即点类的头文件

1头文件中创建一个.h文件且仅保留方法和属性声明删去成员方法中的具体实现

#program once //防止头文件重复包含
#include<iostream>
using namespace std;
using namespace std;
class Point {	//点类
public:
	void setXY(int x, int y);
	int getX();
	int getY();
private:
	int m_X, m_Y;	//x和y坐标
};

2在源文件中创建一个.cpp文件且仅保留方法的实现与头文件相对应需在方法名前加上作用域若不加默认是在全局作用域下则会报错

#include "point.h"    //头文件
void Point::setXY(int x, int y) {	//设置x和y
	m_X = x;
	m_Y = y;
}
int Point::getX() {	//获取x
	return m_X;
}
int Point::getY() {	//获取y
	return m_Y;
}

3 在具体程序使用时需要加上#incude "circle.h"因为circle.h中已带有point.h头文件故可将point.h省略

点和圆关系

#include<iostream>
using namespace std;
class Point {	//点类
public:
	void setXY(int x, int y) {	//设置x和y
		m_X = x;
		m_Y = y;
	}
	int getX() {	//获取x
		return m_X;
	}
	int getY() {	//获取y
		return m_Y;
	}
private:
	int m_X, m_Y;	//x和y坐标
};

class Circle {
public:
	void setCenter(Point p) {	//设置圆心
		m_Center.setXY(p.getX(), p.getY());
	}
	Point getCenter() {	//查看圆心
		return m_Center;
	}
	void setR(int r) {	//设置半径
		m_R = r;
	}
	int getR() {	//查看半径
		return m_R;	
	}
	int calculate(Point p) {
		int dis_X = p.getX() - m_Center.getX();
		int dis_Y = p.getY() - m_Center.getY();
		if ((dis_X * dis_X + dis_Y * dis_Y) > m_R) {
			cout << "点在圆外";
			return 0;	//点在圆外
		}
		else if ((dis_X * dis_X + dis_Y * dis_Y) == m_R) {
			cout << "点在圆上";
			return 1;	//点在圆上
		}
		else {
			cout << "点在圆内";
			return 2;	//点在圆内
		}
	}
private:
	Point m_Center;	//圆心坐标
	int m_R;	//半径
};

int main() {
	Point p;
	p.setXY(10, 11);
	Circle c;
	Point t;
	t.setXY(10, 0);
	c.setCenter(t);
	c.calculate(p);
}

2.对象的初始化和清理

2.1.构造函数和析构函数

1.c++利用了构造函数析构函数进行对象的初始化和清理

2.构造函数和析构函数将会被编译器自动调用如果我们不提供构造和析构编译器会提供默认的的构造函数和析构函数并且其是空实现

3.构造函数主要作用在于创建对象时为对象的成员属性赋值构造函数由编译器自动调用无须手动调用

构造函数语法

类名(){}

1构造函数没有返回值也不写void与普通函数区别

2函数名称与类名相同

3构造函数可以有参数因此可以发生重载允许构造函数重载

4程序在申明对象时候会自动调用构造无须手动调用而且只会调用一次

4.析构函数主要作用在于对象销毁前系统自动调用执行一些清理工作

析构函数语法 

~类名(){}

1 析构函数没有返回值也不写void

2函数名称与类名相同在名称前加上符号 ~与构造函数区别

3析构函数不可以有参数因此不可以发生重载与构造函数区别

4程序在对象销毁前会自动调用析构无须手动调用而且只会调用一次

5.1在函数test01中Person p属于局部变量被存储在栈区在test01执行完后test01占有的栈区空间将被释放使用权被回收此时将会执行p的析构函数Person类的析构函数p占用的内存空间属于test01占用的栈区空间

2而若Person p存在于main函数中则会先被卡在system("pause")即只会执行构造函数而不会执行析构函数因为程序还在运行即p还在使用中未被释放在按下任意键后main函数执行完毕此时才会执行p的析构函数

原因析构函数在对象销毁前系统自动调用

class Person
{
public:
    //构造函数
    Person()
    {
        cout << "Person的构造函数调用" << endl;
    }
    //析构函数
    ~Person()
    {
        cout << "Person的析构函数调用" << endl;
    }

};

void test01()
{
    //p存放在栈中在test01执行完后释放p的内存空间即会执行p的析构函数
    Person p;
}

int main() {

    test01();

    system("pause");

    return 0;
}

2.2.构造函数的分类和调用

1.编译器在程序员未提供构造函数时默认提供的是无参的构造函数空实现

2.拷贝构造函数的作用是将传入参数的成员属性赋值给自己相当于传入一个有属性的类来初始化我这个类的属性其中用引用或取地址是为防止出现副本而增加内存开销用const是为防止本身被改变

3.为什么拷贝构造函数中要用引用传递不用值传递 

因为值传递本身也是会创建一个副本 创造副本的时候又会执行一次拷贝构造函数 然后一直递归故不用值传递

4.使用括号法调用构造函数时调用无参构造函数不能加括号编译器会认为是返回值为某类的函数的声明

//这样调用无参的构造函数时错误的
//编译器会认为这是函数的声明即返回值为Person类的函数P
Person P();

//返回值为空的P函数的声明
void P();

//调用无参的构造函数不加括号
Person P;

5. 使用显示法调用构造函数时若没有类去接受申明的类则该类为匿名对象匿名对象会在调用后立刻销毁该行代码执行结束后立即销毁有名的对象会在程序结束的时候销毁两者生存周期不同

Person p = Person(10);    //有名对象
Person(10);    //匿名对象

6.不要在匿名对象里以拷贝构造函数的格式创建已声明的对象若该对象未被申明则可以

编译器会创建另一个对象p2相当于去掉括号但由于p2已经被申明故重定义

Person p1;
Person p2 = Person(10);
//Person(P2);
//错误编译器会认为Person(P2)等价为Person P2即声明Person类的P2

7.隐式转换法编译器将会自动帮你转换成显示法调用匹配的构造函数

Person p4 = 10; // Person p4 = Person(10); 
Person p5 = p4; // Person p5 = Person(p4); 
//1、构造函数分类
// 按照参数分类分为 有参和无参构造   无参又称为默认构造函数
// 按照类型分类分为 普通构造和拷贝构造

class Person {
public:
    //无参默认构造函数
    Person() {
        cout << "无参构造函数!" << endl;
    }
    //有参构造函数
    Person(int a) {
        age = a;
        cout << "有参构造函数!" << endl;
    }
    //拷贝构造函数
    Person(const Person& p) {
        age = p.age;
        cout << "拷贝构造函数!" << endl;
    }
    //析构函数
    ~Person() {
        cout << "析构函数!" << endl;
    }
public:
    int age;
};

//2、构造函数的调用
//调用无参构造函数
void test01() {
    Person p; //调用无参构造函数
}

//调用有参的构造函数
void test02() {

    //2.1  括号法常用
    Person p1(10);
    //注意1调用无参构造函数不能加括号如果加了编译器认为这是一个函数声明
    //Person p2();

    //2.2 显式法
    Person p2 = Person(10); 
    Person p3 = Person(p2);
    //Person(10)单独写就是匿名对象  当前行结束之后马上析构
    Person(10);

    //2.3 隐式转换法
    Person p4 = 10; // Person p4 = Person(10); 
    Person p5 = p4; // Person p5 = Person(p4); 

    //注意2不能利用 拷贝构造函数 初始化匿名对象 编译器认为是对象声明
    //Person p5(p4);
}

int main() {

    test01();
    //test02();

    system("pause");

    return 0;
}

2.3.拷贝构造函数调用时机

1.使用一个已经创建完毕的对象来初始化一个新对象

//1. 使用一个已经创建完毕的对象来初始化一个新对象
void test01() {

    //调用有参构造函数声明一个Person类对象man
    Person man(100);

    //调用拷贝构造函数声明一个Person类对象newmanman的副本
    Person newman(man); //调用拷贝构造函数

    //相当于Person newman2 = Person(man)
    //调用拷贝构造函数声明一个Person类对象newman2man的副本
    Person newman2 = man; 

    //Person newman3;
    //newman3 = man; //不是调用拷贝构造函数赋值操作
}

2.值传递的方式给函数参数传值作为实参的副本

值传递相当于Person p1 = p即拷贝构造函数隐式写法

值传递的本质是拷贝一个临时的副本即通过调用拷贝构造函数完成创建副本的操作

//编译器通过Person类的拷贝构造函数创建了一个副本p1
//即Person p1 = pdoWork函数内部实际上是对p1进行修改
void doWork(Person p1) {
    ...
}

void test02() {
    Person p; //声明Person类的p调用无参构造函数
    doWork(p); //调用doWork函数传入参数p
}

3.以值方式返回局部对象

class Person {
public:
    Person() {
        cout << "无参构造函数!" << endl;
        mAge = 0;
    }
    Person(int age) {
        cout << "有参构造函数!" << endl;
        mAge = age;
    }
    Person(const Person& p) {
        cout << "拷贝构造函数!" << endl;
        mAge = p.mAge;
    }
    
    //析构函数在释放内存之前调用
    ~Person() {
        cout << "析构函数!" << endl;
    }
public:
    int mAge;
};

//1. 使用一个已经创建完毕的对象来初始化一个新对象
void test01() {

    Person man(100); //p对象已经创建完毕
    Person newman(man); //调用拷贝构造函数
    Person newman2 = man; //拷贝构造

    //Person newman3;
    //newman3 = man; //不是调用拷贝构造函数赋值操作
}

//2. 值传递的方式给函数参数传值
//相当于Person p1 = p;
void doWork(Person p1) {}
void test02() {
    Person p; //无参构造函数
    doWork(p);
}

//3. 以值方式返回局部对象
Person doWork2()
{
    Person p1;
    cout << (int *)&p1 << endl;
    return p1;
}

void test03()
{
    Person p = doWork2();
    cout << (int *)&p << endl;
}


int main() {

    //test01();
    //test02();
    test03();

    system("pause");

    return 0;
}

2.4.构造函数的调用规则

1.默认情况下c++编译器至少给一个类添加3个函数

1默认构造函数(无参函数体为空)空实现

2默认析构函数(无参函数体为空)空实现

3默认拷贝构造函数对属性进行值拷贝默认的拷贝构造函数将会对每个成员属性进行复制浅拷贝

2.构造函数调用规则如下

1如果用户定义有参构造函数c++不在提供默认无参构造但是会提供默认拷贝构造相当于系统将有参构造和无参构造视为同一类型的你提供了系统就不提供了。拷贝构造函数和析构函数又视为另外两种类型

2如果用户定义拷贝构造函数c++不会再提供其他构造函数定义类不能只写拷贝构造函数需写无参/有参构造函数

2.5.深拷贝和浅拷贝

1.浅拷贝简单的赋值拷贝操作等号赋值操作等编译器提供的默认拷贝构造函数实现的是浅拷贝对数据进行逐字节的复制

//浅拷贝
m_age = p.m_age;
m_Height = p.m_Height;

2.深拷贝在堆区重新申请空间进行拷贝操作

1为什么开辟到堆区因为堆区的数据可以手动释放在函数体外也可以使用区别于局部变量实际应用中会用到堆区用到堆区而且又需要拷贝的时候要防止指针悬挂的问题指针悬挂即存在指针指向了一块该进程没有使用权限的内存空间

2new申请了一片堆区的内存空间存储数据并将这片内存空间的地址返回需要一个指针去接受指向这个地址

3.

int  *Height = new int (160);

//两者等价
int *Height ;
Height = new int(10);

4.输出Height为输出该指针指向的内存空间的地址输出*Height为输出该指针指向的内存空间所存储的数据解引用

①输出指针输出指针指向的地址

②输出解引用输出指针指向的地址存放的数据

int a = 10;
//指向存放a地址的指针ptr
int *ptr = &a;

//输出指针ptr输出的是存放a的地址
cout << ptr << endl;

//输出解引用输出的是a
cout << *ptr << endl;

5.构造函数使用new在堆区申请一片内存空间存储变量

在函数执行后析构函数使用delete释放该变量在堆区占用的内存空间

当类中的构造函数使用new后需要相应的使用delete在析构函数中释放若没有使用delete释放则编译器只会释放该类中用于接收new而声明的指针变量在栈区而不能释放new的空间在堆区就会造成内存泄漏堆区空间需要手动释放

6.默认拷贝构造函数浅拷贝导致的问题

①p1在进行有参初始化时在堆区申请了一个空间p1的m_height指针就指向这个空间

②p2在进行拷贝初始化时使用的是编译器提供的浅拷贝默认拷贝构造函数

浅拷贝是对成员变量的简单赋值对数据进行逐字节的拷贝所以p2的m_height指针的值等于p1的height指针即两个height指针指向堆区的同一个地址函数test01结束后p1和p2把同一个空间释放了两次所以程序崩了

因栈特性先进后出先进行p2的释放后再进行p1的释放

解决方法设置拷贝构造函数采用深拷贝的方式在堆区申请一片内存空间存储该数据并让类中的指针指向这片堆区的内存空间每个类的堆区空间相互独立并只释放自己的堆区的空间

⑤其中拷贝构造函数的解释

//1.解引用得到传入参数p中m_heigh指向的地址的值
//2.new申请一片内存空间存放该值
//3.让自己的指针m_Height指向这片堆区
m_Height = new int (*p.m_Height);
class Person {
public:
    //无参默认构造函数
    Person() {
        cout << "无参构造函数!" << endl;
    }
    //有参构造函数
    Person(int age ,int height) {

        cout << "有参构造函数!" << endl;

        m_age = age;
        m_height = new int(height);

    }
    //拷贝构造函数  
    Person(const Person& p) {
        cout << "拷贝构造函数!" << endl;
        //如果不利用深拷贝在堆区创建新内存会导致浅拷贝带来的重复释放堆区问题
        m_age = p.m_age;
        m_height = new int(*p.m_height);

    }

    //析构函数
    ~Person() {
        cout << "析构函数!" << endl;
        if (m_height != NULL)
        {
            delete m_height;
        }
    }
public:
    int m_age;
    int* m_height;
};

void test01()
{
    Person p1(18, 180);

    Person p2(p1);

    cout << "p1的年龄 " << p1.m_age << " 身高 " << *p1.m_height << endl;

    cout << "p2的年龄 " << p2.m_age << " 身高 " << *p2.m_height << endl;
}

int main() {

    test01();

    system("pause");

    return 0;
}

2.6.初始化列表

作用C++提供了初始化列表语法用来初始化属性类似构造函数的作用

构造函数()属性1(值1),属性2值2... {}

使用初始化列表的好处初始化列表相当于直接声明一个有初始值的类型省略了赋值操作

1.类成员中存在常量如 const int a;   因为常量只能初始化不能赋值

2.类成员中存在引用同样只能使用初始化不能赋值

3.提高效率 

Person p(1, 2, 3) → 1赋值给a → a赋值给m_A

class Person {
public:

    传统方式初始化
    //Person(int a, int b, int c) {
    //    m_A = a;
    //    m_B = b;
    //    m_C = c;
    //}

    //初始化列表方式初始化
    Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}
    void PrintPerson() {
        cout << "mA:" << m_A << endl;
        cout << "mB:" << m_B << endl;
        cout << "mC:" << m_C << endl;
    }
private:
    int m_A;
    int m_B;
    int m_C;
};

int main() {

    Person p(1, 2, 3);
    p.PrintPerson();


    system("pause");

    return 0;
}

2.7.类对象作为类成员

①Person类中的初始化列表Phone m_phone = pName 隐式转换法

等价于 Phone m_phone = Phone(pName)显示法

等价于 Phone m_phone(pName)括号法

调用Phone类中的有参构造

②当其他类作为本类成员构造时候先构造其他类对的象再构造自身

析构顺序则与构造顺序相反栈先进后出的特性

class Phone
{
public:
    Phone(string name)
    {
        m_PhoneName = name;
        cout << "Phone构造" << endl;
    }

    ~Phone()
    {
        cout << "Phone析构" << endl;
    }

    string m_PhoneName;

};


class Person
{
public:

    //初始化列表可以告诉编译器调用哪一个构造函数
    Person(string name, string pName) :m_Name(name), m_Phone(pName)
    {
        cout << "Person构造" << endl;
    }

    ~Person()
    {
        cout << "Person析构" << endl;
    }

    void playGame()
    {
        cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 牌手机! " << endl;
    }

    string m_Name;
    Phone m_Phone;

};
void test01()
{
    //当类中成员是其他类对象时我们称该成员为 对象成员
    //构造的顺序是 先调用对象成员的构造再调用本类构造
    //析构顺序与构造相反
    Person p("张三" , "苹果X");
    p.playGame();

}


int main() {

    test01();

    system("pause");

    return 0;
}

2.8.静态成员

静态成员就是在成员变量和成员函数前加上关键字static称为静态成员

1.静态成员变量

1所有对象共享同一份数据A和B共用一份数据若A或者B修改则另一方使用时使用的就是修改后的数据即静态变量不属于某个特定的对象因此有两种访问方式

 ①通过某个对象访问也就是之前成员变量的访问方式

Person p;
cout << p.m_A << endl;

通过类名访问需要指明哪个作用域下Person::表示在Person作用域下

cout << Person::m_A << endl;

2在编译阶段分配内存程序运行前就在全局区中分配好内存

3类内声明类外初始化必须要有初始值

class Person{
public:
    static int m_A;    //类内申明
}

//A::member表示A类中的成员member
//B::member表示B类中的成员member
//Person::m_A表示Person作用域下的成员m_A
int Person::m_A = 100;    //类外初始化

void test(){
    Person P1;
    Person p2;
    //修改p2的m_A为200此时因为Person中的m_A为静态变量
    //即p1和p2共用同一个变量m_A
    //因此p1的m_A也被修改为200
    p2.m_A = 200;    
}

4静态成员变量仍然可以有访问权限当不为pubilc时类外可以对其初始化但是不能访问

class Person
{

public:

    static int m_A; //静态成员变量

    //静态成员变量特点
    //1 在编译阶段分配内存
    //2 类内声明类外初始化
    //3 所有对象共享同一份数据

private:
    static int m_B; //静态成员变量也是有访问权限的
};
int Person::m_A = 10;
int Person::m_B = 10;

void test01()
{
    //静态成员变量两种访问方式

    //1、通过对象
    Person p1;
    p1.m_A = 100;
    cout << "p1.m_A = " << p1.m_A << endl;

    Person p2;
    p2.m_A = 200;
    cout << "p1.m_A = " << p1.m_A << endl; //共享同一份数据
    cout << "p2.m_A = " << p2.m_A << endl;

    //2、通过类名
    cout << "m_A = " << Person::m_A << endl;


    //cout << "m_B = " << Person::m_B << endl; //私有权限访问不到
}

int main() {

    test01();

    system("pause");

    return 0;
}

2.静态成员函数

1所有对象共享同一个函数

2静态成员函数只能访问静态成员变量无法访问非静态成员变量

①静态函数不是属于某个特定对象的即所有该类的所有对象都可以调用静态函数因此编译器不知道非静态成员变量m_B是哪个对象的成员引发歧义

②静态static成员函数它只属于类本身不属于每一个对象实例独立存在。非静态成员仅当实例化对象之后才存在。静态成员函数产生在前非静态成员函数产生在后静态函数无法访问一个不存在的东西。

class Person
{
public:
    static void func()
    {
        cout << "func调用" << endl;
        m_A = 100;
        //m_B = 100; //错误不可以访问非静态成员变量
    }
    static int m_A; //静态成员变量
    int m_B; //非静态成员变量
};

3静态成员函数不属于某个特定的对象有两种访问方式与静态成员变量相同

①通过某个对象访问

Person p;
cout << p.func() << endl;

②通过类名访问

cout << Person::func() << endl;

4静态成员函数可以设置访问权限 

class Person
{

public:

    //静态成员函数特点
    //1 程序共享一个函数
    //2 静态成员函数只能访问静态成员变量

    static void func()
    {
        cout << "func调用" << endl;
        m_A = 100;
        //m_B = 100; //错误不可以访问非静态成员变量
    }

    static int m_A; //静态成员变量
    int m_B; // 
private:

    //静态成员函数也是有访问权限的
    static void func2()
    {
        cout << "func2调用" << endl;
    }
};
int Person::m_A = 10;


void test01()
{
    //静态成员变量两种访问方式

    //1、通过对象
    Person p1;
    p1.func();

    //2、通过类名
    Person::func();


    //Person::func2(); //私有权限访问不到
}

int main() {

    test01();

    system("pause");

    return 0;
}

3.C++对象模型和this指针

3.1.成员变量和成员函数分开存储

1.c++编译器会为每一个空对象也分配一个字节空间用于区分不同的对象每个空对象也应该有一个独一无二的内存地址

class Person{

};
Person p;     //p为空对象占用空间为1B

2.当定义了一个非静态成员变量时分配给对象的内存空间所占字节由变量的数据类型决定需内存对齐

①结构体或类中的内存分配默认是按照4个字节的倍数进行分配

②内存对齐在32位操作系统下我们普遍用的gcc编译器和vs编译器都是默认按照4个大小进行内存对齐的

1比如有两个成员  int a   char b 如果按大小相加的话应该为5字节但由于内存对齐是8个字节

2比如有两个成员  char a char b   这时的类的大小就为2字节因为这个时候成员都是char型的都占一个字节的空间本身就对齐了不要内存对齐操作

3比如有四个成员3个char型一个int型内存对齐后为8

class Person{
    int m_A;    
};
Person p;     //p占用空间为4B

3.只有非静态成员变量是属于类的对象占用对象的空间int m_A

静态成员变量static int m_A、静态成员函数static void func()和非静态成员函数void func()不属于类的对象不占用对象空间static关键字和函数

4.所有函数共享一个函数实例

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

3.2.this指针的概念

this指针的本质是指针常量指针的指向的地址是不可以更改的指向的地址存放的数据可以更改

1.this指针是隐含每一个非静态成员函数内的一种指针this指针指向被调用的成员函数所属的对象

2.this指针不需要定义直接使用即可成员函数内部都有一个this指针

3.this指针的用途

1当形参和成员变量同名时可用this指针来区分相当于python中的self解决形参和成员变量重名时编译器无法区分的问题

this修饰的变量即 this->成员变量(this->age) 指的是本对象的成员变量

没有被this修饰的变量即 变量(age) 指的是形式参数

//采用this指针
class Person
{
public:

    Person(int age)    //传入形式参数age
    {
        //当形参和成员变量同名时可用this指针来区分
        //传入Person构造函数中的形式参数age赋值给该类中的age变量
        this->age = age;
    }
    int age;
};

//不适用this指针
class Person{
public:
    //编译器会认为Person构造函数中的形式参数age和赋值操作的左右两个age
    //这三个age都是同一个age即都是形式参数age
    //故执行完Person构造函数后Person类中的age并没有被赋值
    Person(int age){
        age = age;
    }
    int age;
};

2返回*this可在类的非静态成员函数中返回对象本身

①构造函数中参数使用引用传递 / 值传递

引用传递函数体内部的形参p与传入的实参p1是同一个对象

值传递利用默认拷贝构造函数构造一个p1的副本p即p和p1是不同的对象

②构造函数中返回值使用引用返回 / 值返回

引用返回不发生拷贝返回的是p2本体始终对p2进行加每次返回的都是p2的本体最后p2.age = 40

值返回发生拷贝返回的是p2的副本只对p2进行一次加只有第一次的this指针指向的是p2的本体因此只对p2进行一次加最后p2.age = 20第二次是对p2'通过默认拷贝构造函数浅拷贝得到的p2的副本进行加操作第三次是对p2''通过默认拷贝构造函数浅拷贝得到的p2'的副本进行操作且p2''.age = 40但p2.age = 20

注意这里是按引用传递所以没有发生拷贝相当于传了对象本身然后按引用返回返回的即对象本身

②this是指向对象p2的指针即this的值为p2存储的地址*this则为这个地址的数据即对象p2

class Person
{
public:

    Person(int age)
    {
        //1、当形参和成员变量同名时可用this指针来区分
        this->age = age;
    }

    Person& PersonAddPerson(Person p)
    {
        this->age += p.age;
        //返回对象本身
        return *this;
    }

    int age;
};

void test01()
{
    Person p1(10);
    cout << "p1.age = " << p1.age << endl;

    Person p2(10);
    p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);
    cout << "p2.age = " << p2.age << endl;
}

int main() {

    test01();

    system("pause");

    return 0;
}

3.3.空指针访问成员函数

1.空指针可以调用成员函数可以是cout输出语句等但该成员函数不能访问成员变量

Person *p = NULL即this = NULL而执行ShowPerson函数时被要求通过this访问成员变量mAge出错

可以这么理解你连女朋友都没有怎么会有女朋友的年龄

2.解决方法可以通过在需要访问成员变量的成员函数中添加一个对于this指针指向的if判断判断是否this是否为空指针如果为空则return防止空指针导致程序出错

3.成员函数访问本类成员变量的方式实际上是通过this指针的形式用于强调这是当前对象的成员变量this->成员变量

class Person{
public:
    void test() {
        //编译器实现时时通过指针的方式即this->m_Age = 0;
        m_Age = 0;
    }

    int m_Age;
};
//空指针访问成员函数
class Person {
public:

    void ShowClassName() {
        cout << "我是Person类!" << endl;
    }

    void ShowPerson() {
        if (this == NULL) {    //判断当前对象是否为空指针
            return;
        }
        cout << mAge << endl;    //实际上是this->mAge
    }

public:
    int mAge;
};

void test01()
{
    Person * p = NULL;
    p->ShowClassName(); //空指针可以调用成员函数
    //空指针且成员函数中用到了this指针就不可以了
    //需增加个if判断指针是否为空为空则返回可以避免错误
    p->ShowPerson();
}

int main() {

    test01();

    system("pause");

    return 0;
}

3.4.const修饰成员函数

常函数在成员函数后加const修饰的是this的指向让指针指向的地址和值都不能更改

常对象在申明时加const使得成员属性不能被更改

2.1原来的this是指针常量Person * const this其中const修饰的是this即不允许修改this指针指向的地址但可以修改this指针指向的地址中存放的数据

因此this->m_A = 100;修改其指向地址的值是允许的而this = NULL;即即修改其指向的地址是不允许的

2修改之后this指针是常量指针常量const Person * const this体现在成员函数中则是const添加在括号后其中第二个const依然修饰的是this即不允许修改this指针指向的地址而第一个const则使得this指针指向的地址存放的数据也不能更改

this指针本身就是指针常量现在再用const修饰就是常量指针常量指针指向的地址和该地址的数据就不能再修改了

3.在成员属性前面加上mutable关键字则可以使得该成员属性在常函数/常对象中可以被修改

4.常对象只能调用常函数如果常对象能够调用普通成员函数而该普通成员函数中含有修改对象的成员属性的语句则就可以通过该函数修改成员属性与常对象不能修改成员属性的特性相悖即常对象如果允许调用非常函数则可能修改属性

class Person {
public:
	Person() {
		m_A = 0;
		m_B = 0;
	}

	//this指针的本质是一个指针常量指针的指向不可修改
	//如果想让指针指向的值也不可以修改需要声明常函数
	void ShowPerson() const {
		//const Type* const pointer;
		//this = NULL; //不能修改指针的指向 Person* const this;
		//this->mA = 100; //但是this指针指向的对象的数据是可以修改的

		//const修饰成员函数表示指针指向的内存空间的数据不能修改除了mutable修饰的变量
		this->m_B = 100;
	}

	void MyFunc() const {
		//m_A = 10000;
        //错误不允许在常函数中修改成员属性
	}

public:
	int m_A;
	mutable int m_B; //mutable 可修改 可变的
};


//const修饰对象  常对象
void test01() {

	const Person person; //常量对象  
	cout << person.m_A << endl;
	//person.m_A = 100; //常对象不能修改成员变量的值,但是可以访问
	person.m_B = 100; //但是常对象可以修改mutable修饰成员变量

	//常对象访问成员函数
	person.MyFunc(); 

}

int main() {

	test01();

	system("pause");

	return 0;
}

4.友元

在程序里有些私有属性也想让类外特殊的一些函数或者类进行访问就需要用到友元的技术

友元的目的就是让一个函数或者类访问另一个类中私有成员

友元的关键字为 ==friend==

4.1.全局函数做友元

全局函数做友元需要加上friend在类里声明表示该全局函数可以访问私有变量

class Building
{
    //告诉编译器 goodGay全局函数 是 Building类的好朋友可以访问类中的私有内容
    friend void goodGay(Building * building);

public:

    Building()
    {
        this->m_SittingRoom = "客厅";
        this->m_BedRoom = "卧室";
    }


public:
    string m_SittingRoom; //客厅

private:
    string m_BedRoom; //卧室
};


void goodGay(Building * building)
{
    cout << "好基友正在访问 " << building->m_SittingRoom << endl;
    cout << "好基友正在访问 " << building->m_BedRoom << endl;
}


void test01()
{
    Building b;
    goodGay(&b);
}

int main(){

    test01();

    system("pause");
    return 0;
}

4.2.类做友元

1.类或结构体的前向声明只能用来定义指针对象或引用且不能解引用因为编译到这里时还没有发现定义因此不知道该类或者结构的内部成员没有办法具体的构造一个对象所以会报错

class Building;    //类的声明
Building *p;    //一个类只有声明则可以创建指针
Building *&t = p;    //一个类只有声明可以创建引用
//Building a;是错误的一个类只有声明无法创建实例化对象

2.类内声明类外实现函数类内可以只给出成员函数的实现类外再给出具体的实现但是在类外实现的函数需要指明作用域即类名 :: 函数名 (参数列表)

class Building {
public:
    Building();    //类内构造函数的声明
    void visit();    //类内成员函数的声明
    
    int m_A;    //类内成员属性的声明
}

Building::Building(){    //类外构造函数的实现
    ...
}

void Building::visit(){    //类外成员函数的实现
    ...
}

3.类做友元就是将A类前添加friend关键字并放在B类中这样就能使得A类访问B类的私有成员变量

class Building;
class goodGay
{
public:

    goodGay();
    void visit();

private:
    Building *building;
};


class Building
{
    //告诉编译器 goodGay类是Building类的好朋友可以访问到Building类中私有内容
    friend class goodGay;

public:
    Building();

public:
    string m_SittingRoom; //客厅
private:
    string m_BedRoom;//卧室
};

Building::Building()
{
    this->m_SittingRoom = "客厅";
    this->m_BedRoom = "卧室";
}

goodGay::goodGay()
{
    building = new Building;
}

void goodGay::visit()
{
    cout << "好基友正在访问" << building->m_SittingRoom << endl;
    cout << "好基友正在访问" << building->m_BedRoom << endl;
}

void test01()
{
    goodGay gg;
    gg.visit();

}

int main(){

    test01();

    system("pause");
    return 0;
}

4.3.成员函数做友元

1.A->B指针指向所指对象的各种东西时用 -> 

A::B表示A作用域下的B

A.B表示A的成员B . 

2.1因为Goodgay类需要声明Building类变量所以Building类必须Goodgay类之前声明前置声明但前置声明只能是以引用形式或者指针形式声明而不能是实例化对象

2因为Building的定义中需要将Goodgay类的成员函数声明成友元成员函数所以Building类必须Goodgay类之后定义即Building类需要使用到具体的Goodgay类

3因为Goodgay中的构造函数需要调用Building的构造函数所以Goodgay类中构造函数的实现必须在Building类的定义之后

程序是一行一行顺序执行的如果顺序颠倒将会出错

3.goodGay类的成员函数visit是Building类的友元因此可以访问Building类的私有成员变量

goodGay类的成员函数visit不是Building类的友元因此不可以访问Building类的私有成员变量

class Building;
class goodGay
{
public:

    goodGay();
    void visit(); //只让visit函数作为Building的好朋友可以发访问Building中私有内容
    void visit2(); 

private:
    Building *building;
};


class Building
{
    //告诉编译器  goodGay类中的visit成员函数 是Building好朋友可以访问私有内容
    friend void goodGay::visit();

public:
    Building();

public:
    string m_SittingRoom; //客厅
private:
    string m_BedRoom;//卧室
};

Building::Building()
{
    this->m_SittingRoom = "客厅";
    this->m_BedRoom = "卧室";
}

goodGay::goodGay()
{
    building = new Building;
}

void goodGay::visit()
{
    cout << "好基友正在访问" << building->m_SittingRoom << endl;
    cout << "好基友正在访问" << building->m_BedRoom << endl;
}

void goodGay::visit2()
{
    cout << "好基友正在访问" << building->m_SittingRoom << endl;
    //cout << "好基友正在访问" << building->m_BedRoom << endl;
}

void test01()
{
    goodGay  gg;
    gg.visit();

}

int main(){

    test01();

    system("pause");
    return 0;
}

5.运算符重载

1.编译器只能实现特定数据类型int、float等等的加减乘除等操作而编译器不能实现自定义类的加减乘除等操作编译器也不知道该如何正确的实现将会出错没有与这些操作数匹配的运算符需要通过运算符重载的方式实现或者说规定对类的加减乘除等操作

2.可以通过成员函数的方式和全局函数的方式实现运算符重载

1成员函数写在类内可以使用this指针this指向的是调用该函数的实例对象只需传入一个变量

2全局函数无法使用this指针需要传入两个变量

3可以通过正常的运算方式+-*/例person p3 = p2 + p1简化调用实现运算符重载的成员函数/全局函数通过指定函数名的方式编译器帮忙实现转换也可以通过传入参数的方式调用

//成员函数方式
Person p3 = p2 + p1;  //相当于 p2.operaor+(p1)

//全局函数方式
Person p4 = p3 + 10; //相当于 operator+(p3,10)

3.运算符重载也允许实现函数重载且与函数重载规则一致

4.对于内置的数据类型的表达式的的运算符是不可能改变的intfloat等数据类型的运算符不能重新定义

5.不要滥用运算符重载加号运算符重载不能定义为相减

5.1.加号运算符重载

1.实际上就是把p2传入p1的成员函数然后拷贝相加再把结果返回

2.需要使用指定的函数名即operator+

3.使用引用传递是减少内存占用使用const是因使用引用传递防止在函数体内部中对引用的值进行修改

class Person {
public:
    Person() {};
    Person(int a, int b)
    {
        this->m_A = a;
        this->m_B = b;
    }
    //成员函数实现 + 号运算符重载
    Person operator+(const Person& p) {
        Person 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;
};

//全局函数实现 + 号运算符重载
//Person operator+(const Person& p1, const Person& p2) {
//    Person temp(0, 0);
//    temp.m_A = p1.m_A + p2.m_A;
//    temp.m_B = p1.m_B + p2.m_B;
//    return temp;
//}

//运算符重载 可以发生函数重载 
Person operator+(const Person& p2, int val)  
{
    Person temp;
    temp.m_A = p2.m_A + val;
    temp.m_B = p2.m_B + val;
    return temp;
}

void test() {

    Person p1(10, 10);
    Person p2(20, 20);

    //成员函数方式
    Person p3 = p2 + p1;  //相当于 p2.operaor+(p1)
    cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;


    Person p4 = p3 + 10; //相当于 operator+(p3,10)
    cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;

}

int main() {

    test();

    system("pause");

    return 0;
}

5.2.左移运算符重载

如果不重载左移运算符则无法通过cout << 输出自定义类编译器没有与这些操作数匹配的运算符

1.左移运算符重载只能通过全局函数

1cout << X输出的对象 这种情况是cout在调用operator<<而不是X输出的对象在调用operator<<

2通过比较加号运算符重载的成员函数形式p.operator+(a) 等价于 p+a可以证明如果通过成员函数重在左移运算符则会出现p.operator<<(cout)即p<<cout与我们想要实现的方式cout << p相反故左移运算符不能通过成员函数重载

2.cout的类型是ostream即ostream cout且cout全局只能有一个而值传递的方式会调用默认拷贝构造函数创建一个cout的副本故需要使用引用传递的方式

3.如果要链式调用返回值就要作为下一个调用的参数返回的对象必须为本体相应的如果返回值类型为void则不能实现链式调用即不允许cout << p << endl;

在之前的加法运算符重载调用链式就可以很好说明这点把返回值和参数都设为同一种类型

但是这里ostream类型必须是引用类型所以返回值必须是引用就可以链式调用

4.在重载左移运算符时通过引用传递的方式传入参数cout此时因为是引用传递可以为cout起别名不一定在函数体内部的参数名必须为cout

5.重载左移运算符配合友元可以实现输出某些私有/保护权限的自定义数据类型

class Person {
    friend ostream& operator<<(ostream& out, Person& p);

public:

    Person(int a, int b)
    {
        this->m_A = a;
        this->m_B = b;
    }

    //成员函数 实现不了  p << cout 不是我们想要的效果
    //void operator<<(Person& p){
    //}

private:
    int m_A;
    int m_B;
};

//全局函数实现左移重载
//ostream对象只能有一个
ostream& operator<<(ostream& out, Person& p) {
    out << "a:" << p.m_A << " b:" << p.m_B;
    return out;
}

void test() {

    Person p1(10, 20);

    cout << p1 << "hello world" << endl; //链式编程
}

int main() {

    test();

    system("pause");

    return 0;
}

5.3.递增运算符重载

1.前置递增++a)先实现递增

后置递增a++后实现递增

int a = 0;
cout << a++ << endl;    //先输出a = 0然后再a = a + 1 = 0 + 1 = 1
cout << a << endl;    //输出a = 1
cout << ++a << endl;    //先a = a + 1 = 1 + 1 = 2再输出a 
cout << a << endl;    //输出a = 2

2.前置运算符是直接递增变量而后置要先拷贝旧值然后递增最后返回旧值

设a = 0

1(a++)++是不被允许的编译器将会提示++需要可修改的左值

①输出a++是可以的将会输出a = 0,

出错的点在括号外的++a++的实现过程是编译器首先将会重新声明一个临时变量存储当前a的值然后进行自增操作最后返回这个临时变量的值而这个临时变量返回后再继续进行括号外的++时将是对这个临时变量进行操作这是不被允许的

由此得出我们进行后置递增时返回值类型应该选择值返回如果不带引用的话那么相当于是进行了一次拷贝构造函数从而使得生成了一个新的MyInteger类型的数据如果加了引用之后那么相当于使用的还是temp这个数据由于局部会释放会出错

同时这也导致了这段代码中的左移运算符重载的第二个参数中使用的是值传递而不是引用传递原因就是因为后置++是值返回若采用引用返回则函数调用结束后该函数的局部变量所占用的内存空间被释放不再拥有对这片内存空间的操作权限即导致出错

2++(++a)是允许的

①值得注意的是在默认的前置++中++(++a)的过程始终是对同一个a进行前置++操作

②如果采用值返回的方式则调用前置++函数时函数将会调用拷贝构造函数构造一个匿名对象并且在执行结束后返回这个匿名对象与传入的对象并不是同一个对象因此实际上只会进行一次前置++操作但是能够输出两次前置++的结果因为第二次前置++操作是传入的参数是这个匿名对象

如果采用引用返回的方式则与默认的前置++相同始终都会对同一个对象操作

故前置++需要使用引用返回方式

图为值传递每次传入对象和函数体内部实际操作的对象并不是一个对象而是传入对象的拷贝

int a = 0;    
//1.进行++a即a = 1
//2.输出a = 1
//3.进行++a即a = 2;
cout << ++(++a) << endl;

3.前置++和后置++需要通过占位参数的方式函数重载

原因前置++和后置++的函数名相同但是不能通过返回值类型的不同区分函数重载

函数重载条件同一作用域函数名相同参数类型不同或个数不同或顺序不同

class MyInteger {

    friend ostream& operator<<(ostream& out, MyInteger myint);

public:
    MyInteger() {
        m_Num = 0;
    }
    //前置++
    MyInteger& operator++() {
        //先++
        m_Num++;
        //再返回
        return *this;
    }

    //后置++
    MyInteger operator++(int) {
        //先返回
        MyInteger temp = *this; //记录当前本身的值然后让本身的值加1但是返回的是以前的值达到先返回后++
        m_Num++;
        return temp;
    }

private:
    int m_Num;
};


ostream& operator<<(ostream& out, MyInteger myint) {
    out << myint.m_Num;
    return out;
}


//前置++ 先++ 再返回
void test01() {
    MyInteger myInt;
    cout << ++myInt << endl;
    cout << myInt << endl;
}

//后置++ 先返回 再++
void test02() {

    MyInteger myInt;
    cout << myInt++ << endl;
    cout << myInt << endl;
}

int main() {

    test01();
    //test02();

    system("pause");

    return 0;
}

5.4.赋值运算符重载

1.c++编译器至少给一个类添加4个函数

1默认构造函数(无参函数体为空)

2默认析构函数(无参函数体为空)

3默认拷贝构造函数对属性进行值拷贝浅拷贝逐字节的复制

4赋值运算符 operator=, 对属性进行值拷贝浅拷贝逐字节的复制

2.1①代码中Person类的成员变量m_Age为int型的指针因为编译器默认提供的赋值运算符使用的是浅拷贝即当执行p2 = p1时实际上执行的是p2.m_Age = p1.m_Age即将p1的指针指向的地址直接赋值给了p2的指针指针的值存储的是地址这就导致p2和p1都指向同一个地址即p1指向的地址虽然改变了指针的值但实际上只是改变了指针的指向且原p2指向的地址的数据仍然在那里造成内存泄漏

②p1和p2的指针是在经过赋值后虽然指向的是相同的地址但是仍然不同的两个指针体现在两者的地址不同对其中一个指针的修改并不影响对另外一个这里的修改指的是修改指针的指向

2①此时析构函数中的delete语句在p1执行结束时对该地址进行一次释放而p2执行结束时又对该地址又进行一次释放导致出错不能对同一地址释放两次

②析构函数中对成员变量指针m_Age是否为空的判断针对的是m_Age是否为空指针m_Age是否指向一个地址而非m_Age指向的地址存放的数据是否有效是否被释放

本质就是深拷贝与浅拷贝的问题

解决方法利用深拷贝解决浅拷贝带来的问题即让p2和p1指向的是不同的地址

3.p2 = p1实际上是p2.operator=(p1)即p2作为本类调用自己的成员函数operator=传入参数为p1

4.使用深拷贝重载赋值运算符解决2中浅拷贝引发的问题

1对m_Age执行delete并且将m_Age置空如果直接进行赋值则会导致原来m_Age指向地址的数据仍然占据这个地址即发生内存泄漏

内存泄漏是在计算机科学中由于疏忽或错误造成程序未能释放已经不再使用的内存

2利用new在堆区重新申请一片内存空间进行深拷贝

注意事项

1这里的重载函数的参数一定要以引用或者指针的形式传入对传入的对象p进行操作

2不然在传入的时候进行了一次拷贝将赋值右边p的值传入的时候临时变量记录的p的属性m_Age的地址在调用函数的过程中传入的对象p作为形参编译器将会重新利用默认构造函数浅拷贝一个实参p'即函数实际上是对实参p'进行操作而非p并且p'与p的成员属性相同这个p'是匿名对象即临时变量

3而出了赋值运算符重载函数会进行一次析构这时p的属性new出来的空间已经被释放了p'先进行一次释放p再进行一次释放调用了两次析构函数

4最后结束调用虽然你深拷贝了但是程序还是会崩

5.为了能实现连等即p3 = p2 = p1赋值运算符需要返回自身即return *this链式编程思想

赋值运算符是从右往左赋值故最后的结果为p1、p2和p3的值都为p1

class Person
{
public:

    Person(int age)
    {
        //将年龄数据开辟到堆区
        m_Age = new int(age);
    }

    //重载赋值运算符 
    Person& operator=(Person &p)
    {
        if (m_Age != NULL)
        {
            delete m_Age;
            m_Age = NULL;
        }
        //编译器提供的代码是浅拷贝
        //m_Age = p.m_Age;

        //提供深拷贝 解决浅拷贝的问题
        m_Age = new int(*p.m_Age);

        //返回自身
        return *this;
    }


    ~Person()
    {
        if (m_Age != NULL)
        {
            delete m_Age;
            m_Age = NULL;
        }
    }

    //年龄的指针
    int *m_Age;

};


void test01()
{
    Person p1(18);

    Person p2(20);

    Person p3(30);

    p3 = p2 = p1; //赋值操作

    cout << "p1的年龄为" << *p1.m_Age << endl;

    cout << "p2的年龄为" << *p2.m_Age << endl;

    cout << "p3的年龄为" << *p3.m_Age << endl;
}

int main() {

    test01();

    //int a = 10;
    //int b = 20;
    //int c = 30;

    //c = b = a;
    //cout << "a = " << a << endl;
    //cout << "b = " << b << endl;
    //cout << "c = " << c << endl;

    system("pause");

    return 0;
}

5.5.关系运算符重载

class Person
{
public:
    Person(string name, int age)
    {
        this->m_Name = name;
        this->m_Age = age;
    };

    bool operator==(Person & p)
    {
        if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    bool operator!=(Person & p)
    {
        if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
        {
            return false;
        }
        else
        {
            return true;
        }
    }

    string m_Name;
    int m_Age;
};

void test01()
{
    //int a = 0;
    //int b = 0;

    Person a("孙悟空", 18);
    Person b("孙悟空", 18);

    if (a == b)
    {
        cout << "a和b相等" << endl;
    }
    else
    {
        cout << "a和b不相等" << endl;
    }

    if (a != b)
    {
        cout << "a和b不相等" << endl;
    }
    else
    {
        cout << "a和b相等" << endl;
    }
}


int main() {

    test01();

    system("pause");

    return 0;
}

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: c++