• C++核心编程--对象篇


    4.2、对象

    4.2.1、对象的初始化和清理

    用于对对象进行初始化设置,以及对象销毁前的清理数据的设置。

    1. 构造函数析构函数

      • 防止对象初始化和清理也是非常重要的安全问题
        • 一个对象或变量没有初始化状态,对其使用后果是未知的
        • 同样使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
      • 在c++中会自动被编译器调用这俩个函数,完成对象的初始化和清理工作,因此如果我们不手动提供构造和析构,编译器提供构造函数和析构函数是空实现
    2. 构造函数

      • 主要作用在创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用
      • 写法:
        • 类名(){}
      • 构造函数,没有返回值也不写void
      • 函数名称和类名相同
      • 构造函数可以有参数,因此可以发生重载
      • 程序在调用对象时候会自动调用构造,无需手动调用,而且只会调用一次
    3. 析构函数

      • 主要作用在对象销毁前系统自动调用,执行一些清理
      • 写法:
        • ~类名(){}
      • 析构函数,没有返回值也不写void
      • 函数名称与类名相同,在名称前加上符号
      • 析构函数不可以有参数,因此不可以发生重载
      • 程序在对象前会自动调用析构,无需手动调用,而且只会调用一次
    • ​ 构造函数
    #include
    using namespace std;
    
    
    //-构造函数,没有返回值也不写void
    //- 函数名称和类名相同
    //- 构造函数可以有参数,因此可以发生重载
    //- 程序在调用对象时候会自动调用构造,无需手动调用,而且只会调用一次
    class Person2 {
    public:
    	Person2() {
    		cout << "person2的构造被调用" << endl;
    	}
    	~Person2() {
    		cout << "person2的析构函数被调用" << endl;
    	}
    };
    
    void test05() {
    	Person2 p1;
    }
    
    int main() {
    	test05();//栈上的数据执行完后会自动释放
    
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    image-20230909103757375

    4.2.2、构造函数的分类和调用
    1. 分类方式
      • 按参数分为:有参构造和无参构造
      • 按类型分为:普通构造和拷贝构造
    2. 调用方式
      • 括号法
      • 显示法
      • 隐式转换法
    • 有参构造和无参构造
    class Person2 {
    public:
    	//无参有参构造
    	Person2() {
    		cout << "person2的构造被调用" << endl;
    	}
    	Person2(int a) {
    		cout << "person2的构造被调用" << endl;
    	}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 普通构造和拷贝构造
    
    	//拷贝构造函数
    	Person2(const Person2 &p) {
    		//将传入的对象所有属性拷贝到身上
    		age = p.age;
    	}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 括号法
    //调用
    void test06() {
    	//-括号法
    	Person2 p;
    	//有参构造函数
    	Person2 p1(10);
    	Person2 p2(p1);
    	cout << "P1的年龄:" << p1.age << endl;
    	cout << "P2的年龄:" << p2.age << endl;
    	//- 显示法
    	//- 隐式转换法
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    image-20230909113212647

    调用默认构造函数的时候,不要加()

    加括号会让编译器认为是一个函数的声明

    image-20230909113359068

    • 显示法
    //- 显示法
    Person2 p1;
    Person2 p2 = Person2(10);//有参构造
    Person2 p3 = Person2(p2);//拷贝构造
    
    • 1
    • 2
    • 3
    • 4

    image-20230909114049933

    匿名对象:直接调用不接收。

    特点:当前行执行结束后,系统会立即回收

    image-20230909194028160

    • 注意

      不要利用拷贝构造函数 初始化匿名对象

    //编译器会认为 Person(p3) === Person p3; 对象声明
    Person2(p3);
    
    • 1
    • 2
    • 隐式转换法

      //- 隐式转换法
      //相当于 写了 Person2 p4 = Person(10);
      Person2 p4 = 10;
      Person2 p5 = p4; //拷贝构造
      
      • 1
      • 2
      • 3
      • 4

    image-20230909195038432

    4.2.3、拷贝构造函数调用时机

    c++中拷贝构造函数通常有三种情况

    • 使用一个已经创建完毕的对象来初始化一个新对象
    • 值传递的方式给函数参数传值
    • 以值方式返回局部对象
    1. 使用一个已经创建完毕的对象来初始化一个新对象
    #include
    using namespace std;
    
    class Person3 {
    public:
    	Person3() {
    		cout << "Person3构造函数被调用" << endl;
    	}
    	Person3(int a) {
    		m_Age1 = a;
    		cout << "Person3有参构造函数被调用" << endl;
    	}
    	Person3(const Person3 & p) {
    		m_Age1 = p.m_Age1;
    		cout << "Person3拷贝构造构造函数被调用" << endl;
    	}
    	~Person3() {
    		cout << "Person3析构函数被调用" << endl;
    	}
    	int m_Age1;
    };
    
    //-使用一个已经创建完毕的对象来初始化一个新对象
    void test06() {
    	Person3 p1(20);
    	Person3 p2(p1);
    
    	cout << "P2的年龄:"<
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    image-20230910095020928

    1. 值传递的方式给函数参数传值
    //- 值传递的方式给函数参数传值
    //值传递会拷贝一个副本
    void doWork(Person3 p) {
    
    }
    
    void test07() {
    	Person3 p;
    	doWork(p);
    }
    
    //- 以值方式返回局部对象
    
    
    int main() {
    	test07();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    image-20230910095627143

    1. 以值方式返回局部对象
    //- 以值方式返回局部对象
    
    Person3 doWork2() {
    	//这里返回出去的是一个新的对象,会自己拷贝一个
    	Person3 p1;
    	cout << (int*)&p1 << endl;
    	return p1;
    }
    
    void test08() {
    	//这里可以测试一下是不是同一个对象
    	Person3 p = doWork2();
    	cout << (int*)&p << endl;
    }
    
    int main() {
    	test08();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    image-20230910100245051

    4.2.4、构造函数调用规则

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

    1. 默认构造函数(无参,函数体为空)
    2. 默认析构函数(无参,函数体为空)
    3. 默认拷贝构造函数,对属性进行值拷贝

    构造函数调用规则如下:

    • 如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
    • 如果用户定义拷贝构造函数,c++不会再提供其他构造函数
    1. c++编译器至少给一个类添加3个函数
    #include
    using namespace std;
    //构造函数调用规则
    class Person10 {
    public:
    	Person10() {
    		cout << "person构造函数被调用" << endl;
    	}
    	Person10(int a) {
    		m_age = a;
    		cout << "person构造函数被调用" << endl;
    	}
    	Person10(const Person10 &p) {
    		m_age = p.m_age;
    	}
    	~Person10() {
    		cout << "person析构函数被调用" << endl;
    	}
    	int m_age;
    };
    
    void test09() {
    	Person10 p;
    	p.m_age = 18;
    
    	Person10 p2(p);
    
    	cout << "p2的年龄为:" << p2.m_age << endl;
    
    }
    int main() {
    	test09();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    正常情况下输出结构

    image-20230910105118734

    当我们随意注释掉一个函数,编译器依然可以自动运行,给我们提供一个临时的拷贝构造函数

    image-20230910105209766

    1. 如果用户定义有参构造函数,c++不在提供默认无参构造

    这里我们注释掉自己写的无参构造,运行一下。

    image-20230910105959725

    但是会提供默认拷贝构造

    image-20230910110518838

    1. 如果用户定义拷贝构造函数,c++不会再提供其他构造函数

    我们将拷贝构造以外的所有函数注释掉

    image-20230910110735819

    4.2.5、 深拷贝与浅拷贝
    1. 浅拷贝:简单的赋值拷贝操作
    2. 深拷贝:在堆区重新申请空间,进行拷贝操作

    ​ 浅拷贝带来的问题就是堆区内存重复释放

    ​ 如果我们在函数中开辟了一段堆区数据,我们又用的浅拷贝方式,在运行析构函数时,两块空间都指向同一个地址释放,由于先进后出的原则,前面的释放掉该地址,后面的再释放就会报错,所以需要用到深拷贝来解决该问题。

    image-20230910113706380

    #include
    using namespace std;
    class Person5 {
    public:
    	Person5() {
    		cout << "Person5的构造函数被调用" << endl;
    	}
    	Person5(int a,int height) {
    		m_age = a;
    		//在堆区开辟个空间
    		m_height = new int(height);
    		cout << "Person5的参数构造函数被调用" << endl;
    	}
    	//析构函数的作用就是如果在函数中在堆区开辟了空间,需要手动释放
    	~Person5() {
    		if (m_height!=NULL)
    		{
    			//释放掉空间
    			delete m_height;
    			//把值等于Null 防止野指针出现
    			m_height = NULL;
    		}
    		cout << "Person5的析构函数被调用" << endl;
    	}
    	int m_age;
    	int *m_height;
    };
    
    void test06() {
    	Person5 p1(18,160);
    	cout << "P1的年龄为:" << p1.m_age <<"身高为:" <<*p1.m_height<< endl;
    	Person5 p2(p1);
    	cout << "P1的年龄为:" << p2.m_age << "身高为:" <<*p2.m_height << endl;
    }
    
    int main() {
    
    	test06();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    image-20230910113341232

    ​ 这里可以看到编译器因为重复释放问题报错,为了解决这个重复释放的问题,我们自己写一个拷贝构造函数,解决浅拷贝带来的问题

    	//自己实现拷贝构造函数 解决浅拷贝带来的问题
    	Person5(const Person5 &p) {
    		cout << "Person拷贝构造函数调用" << endl;
    		m_age = p.m_age;
    		//深拷贝操作
    		m_height = new int(*p.m_height);
    
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这里重新开辟空间,大小为p.m_height的解引用

    总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题

    4.2.6、初始化列表

    作用:

    C++提供了初始化列表语法,用来初始化属性

    语法:构造函数():属性1(值1),属性2(值2)…{}

    #include
    using namespace std;
    class Person7 {
    public:
    	//传统赋值操作
    	Person7(int a, int b, int c) {
    		m_a = a;
    		m_b = b;
    		m_c = c;
    	}
        
        //初始化列表初始化属性
    	Person7(int a,int b,int c):m_a(a),m_b(b), m_c(c) {
    
    	}
        
    	int m_a;
    	int m_b;
    	int m_c;
    };
    
    void test07() {
    	Person7 p1(10,20,30);
    	cout << "p1的a值为:"<< p1.m_a << endl;
    	cout << "p1的b值为:"<< p1.m_b << endl;
    	cout << "p1的c值为:"<< p1.m_c << endl;
    }
    
    int main() {
    	test07();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    4.2.7、类对象作为类成员

    c++类中的成员可以是另一个类的对象,我们称为该成员为成员对象

    class A{}
    class B{
        A a;
    }
    
    • 1
    • 2
    • 3
    • 4

    那么在创建B对象时,A,B的构造和解析的顺序是谁先谁后

    
    #include
    #include
    using namespace std;
    
    class Phone {
    public:
    	Phone(string n):mP_Name(n) {
    		cout << "Phone的构造函数调用" << endl;
    	}
    
    	string mP_Name;
    };
    
    class Person0 {
    public:
    	Person0(string name, string pName) :m_Name(name), m_Phone(pName) {
    		cout << "person的构造函数被调用" << endl;
    	}
    	string m_Name;
    	Phone m_Phone;
    };
    
    void test08() {
    	Person0 p("zy", "华为");
    	cout << "name:" << p.m_Name << "手机:" << p.m_Phone.mP_Name<< endl;
    }
    int main() {
    	test08();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    image-20230912162512783

    构造时:我们可以看到是先有成员对象,再有的自身

    析构时:先自身,再成员对象

    也就是像栈的先进后出,后进先出

    4.2.8、静态成员

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

    静态成员分为:

    1. 静态成员变量
      • 所有对象共享同一份数据
      • 再编译阶段分配内存
      • 类内声明,类外初始化
    2. 静态成员函数
      • 所有对象共享同一个函数
      • 静态成员函数只能访问静态成员变量
    • 静态成员变量
    
    #include
    using namespace std;
    
    //静态成员变量
    class Person {
    public:
    	static  int m_A;
    };
    //- 所有对象共享同一份数据
    //- 再编译阶段分配内存
    //- 类内声明,类外初始化!!!必须要类外初始化
    
    int Person::m_A = 100;
    
    void test05() {
    	Person p1;
    	cout << "m_A的值为:" << p1.m_A << endl;
    
    	Person p2;
    	//所有对象共享同一份数据
    	p2.m_A = 200;
    	cout << "p2修改后m_A的值为:" << p1.m_A << endl;
    }
    
    int main() {
    	test05();
    
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    image-20230913093455422

    ​ 静态成员变量 不属于某个对象上,所有对象都共享同一份数据。可通过对象进行访问,也可以直接通过类名进行访问

    	cout << "p2修改后m_A的值为:" << p1.m_A << endl;
    	cout << "p2修改后m_A的值为:" << Person::m_A << endl;
    
    • 1
    • 2

    image-20230913093843106

    ​ 如果写到private权限下那么m_A的值在类外也不可以被访问

    image-20230913094048088

    image-20230913094040955

    • 静态成员函数

    静态函数和静态变量使用方法类似

    #include
    using namespace std;
    
    class Person2 {
    public:
    	static void fun() {
    		//静态成员函数只能访问静态成员变量
    		m_A = 100;
    		//m_B = 200;
    		cout << m_A << endl;
    	}
    	static int m_A;
    	int m_B;
    };
    
    int  Person2::m_A = 0;
    
    void test06() {
    	Person2 p1;
    
    	//两种访问方式
    	p1.fun();
    	Person2::fun();
    
    	Person2 p2;
    	//共用同一个方法
    	p2.fun();
    }
    int main() {
    	//test05();
    	test06();
    	system("pause");
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    静态成员函数只能访问静态成员变量

    image-20230913095907188

    image-20230913095917046

    4.3、C++对象模型和this指针

    4.3.1、成员变量和成员函数分开存储

    在C++中,成员变量和成员函数分开存储

    只有非静态成员变量才属于类的对象上

    1. 在C++中一个空对象占用的内存空间为1字节
    
    #include
    using namespace std;
    
    //成员变量和成员函数分开存储 
    class Person {
    };
    
    void test04() {
    	Person p1;
    	//C++会给每一个空对象分配一字节空间,
    	//是为了区分空对象占用内存的位置
    	cout << sizeof(p1) << endl;
    }
    
    int main() {
    	test04();
    	system("pause");
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    image-20230913101519000

    1. 如果里面声明了一个非静态变量,内存空间会发生改变
    class Person {
    public:
    	int m_A;
    };
    void test05() {
    	Person p2;
    	cout << sizeof(p2) << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    image-20230913101912121

    1. 静态成员变量、静态成员函数、非静态成员函数均不属于类对象上
    class Person {
    public:
    	int m_A;
        
    	static  int m_B;
    	void fun() {};
    	static void fun2() {};
    };
    int Person::m_B = 0;
    
    void test05() {
    	Person p2;
    	cout << sizeof(p2) << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    image-20230913102159875

    4.3.2、this指针概念

    ​ 通过4.3.1我们知道c++中成员变量和成员函数是分开存储的

    ​ 每个非静态成员函数只会诞生一份函数实例,也就是多个同类型的对象会共用一块代码,那么这块代码是如何区分是那个对象调用了自己呢?

    ​ C++通过提供特殊的对象指针this指针,解决上述问题。this指针指向被调用的成员函数所属的对象

    this指针是隐含每一个非静态成员函数内的一种指针

    this指针不需要定义,直接使用即可

    this指针的用途:

    • 当形参和成员变量同名时,可用this指针来区分
    • 在类的非静态成员函数中返回对象本身,可使用return *this
    1. this指针指向被调用的成员函数所属的对象
    #include
    using namespace std;
    
    class Person6 {
    public:
    
    	Person6(int age) {
    		age = age;
    	}
    	int age;
    };
    
    void test06() {
    	Person6 p1(10);
    	cout << "P1的值为:" << p1.age << endl;
    }
    
    int main() {
    	test06();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    这里会出现编译器不认识age属于同一个

    image-20230914162057117

    所以有两种方法,第一种改变名字,第二种用this指向

    image-20230914162139252

    1. 链式编程 *this返回自身
    class Person6 {
    public:
    	Person6(int age) {
    		this->age = age;
    	}
    
    	//做一个年龄+=操作
        //通过 *this返回自身,可以实现链式编程
    	Person6& PersonAddAge(Person6 &p) {
    		this->age += p.age;
            //this 指向的是p2的指针,而*this指向的就是对象本体
    		return *this;
    	}
    	int age;
    };
    void test07() {
    	Person6 p1(10);
    	Person6 p2(10);
    
    		p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
    	cout << p2.age << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    image-20230914163826722

    如果不用解引用方式区返回,输出结果就为20

    image-20230914163851064

    因为编译器会调用拷贝构造函数,创建一个新的对像,不会追加到自身上。

    4.3.3、空指针访问成员函数

    C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针

    如果用到this指针,需要加以判断保证代码的健壮性

    
    #include
    using namespace std;
    
    class Person6 {
    public:
    	void showClassName() {
    		cout << "this is Person class" << endl;
    	}
    	void showPersonAge() {
    		//报错原因是因为传入的指针为Null
    		if (this == NULL)
    		{
    			return;
    		}
    		cout << "age=" < m_Age << endl;
    	}
    	int m_Age;
    };
    
    void test05() {
    	Person6 *p = NULL;
    	p->showClassName();
    	p->showPersonAge();
    }
    int main() {
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    4.3.4、const修饰成员函数

    常函数:

    • 成员函数后加const后我们称为这个函数为常函数
    • 常函数内不可以修改成员成员属性
    • 成员属性声明时加关键字mutable后,在常函数中依然可以修改

    常对象:

    • 声明对象前加const称该对象为常对象
    • 常对象只能调用常函数
    class Person {
    public:
    	//this指针的本质 是指针常量 指针的指向是不可以修改的
    	// const Person * const this;
    	//在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改
    	void showPerson() const {
    		this->m_B = 100;
    		//this = NULL; 
    	}
    
    	void func() {
    		m_A = 100;
    	}
    	int m_A;
    	//特殊变量,即使在常函数中,也可以修改该值,加关键字mutable
    	mutable int m_B; 
    
    };
    
    void test05() {
    	Person p1;
    	p1.showPerson();
    }
    
    void test06() {
    	const Person p; //在对象前加const,变为常对象
    	//p.m_A = 100;
    	p.m_B = 100;//m_B是特殊值,在常对象也可以修改
    
    	//常对象只能调用常函数
    	p.showPerson();
    	//p.func(); //常对象不可以调用普通成员函数,因为普通成员函数可以修改属性
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    4.4、友元

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

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

    关键字:friend

    • 三种实现
      • 全局函数做友元
      • 类做友元
      • 成员函数做友元
    1. 全局函数做友元
    
    #include
    using namespace std;
    
    class Building {
    	//为了让全局函数访问到私有的权限可以利用friend关键字
    	friend void goodGay(Building* building);
    public:
    	Building() {
    		m_sittingRoom = "客厅";
    		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 test05() {
    	Building buiding;
    	goodGay(&buiding);
    }
    int main() {
    	test05();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    image-20230918084840182

    1. 类做友元
    #include
    #include
    using namespace std;
    
    class Building {
    	//为了让类访问到私有的权限可以利用friend关键字
    	friend class GoodGay;
    public:
    	Building();
    
    public:
    	string m_sittingRoom;
    private:
    	string m_BedRoom;
    };
    
    class Building;
    class GoodGay {
    public:
    	GoodGay();
    	//访问Building中的属性
    	void visit();
    
    	Building * building;
    
    };
    
    
    //类外写成员函数
    Building::Building() {
    	m_sittingRoom = "客厅";
    	m_BedRoom = "卧室";
    };
    
    GoodGay::GoodGay() {
    	//指向
    	building = new Building;
    }
    
    void GoodGay::visit() {
    	cout << "类正在访问:" << building->m_sittingRoom << endl;
    	cout << "类正在访问:" << building->m_BedRoom << endl;
    
    };
    
    void test05() {
    	GoodGay gg;
    	gg.visit();
    }
    int main() {
    	test05();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    1. 成员函数做友元
    #include
    #include
    using namespace std;
    
    class Building;
    class GoodGay {
    public:
    
    	GoodGay();
    	//让visit函数可以访问Building中的私有成员
    	void visit();
    	//让visit2函数不可以访问Building中的私有成员
    	void visit2();
    private:
    	Building *building;
    };
    
    class Building {
    	 friend  void GoodGay::visit();
    public:
    	Building();
    public:
    	string m_sittingRoom;
    private:
    	string m_BedRoom;
    };
    
    
    Building::Building() {
    	m_sittingRoom = "客厅";
    	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;
    };
    
    void test07() {
    	GoodGay gg;
    	gg.visit();
    }
    int main() {
    	test07();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    4.5、运算符重载

    概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的类型数据

    1. 对于内置的数据类型的表达式的运算符是不能被修改的

      例:1+1=0 是不被允许的

    2. 不能滥用运算符重载

      例:原本为加法 写成 乘法

    • 关键字:operator
    4.5.1、加号运算符重载
    • operator+
    #include
    using namespace std;
    
    //加号运算符重载
    class Person {
    public:
    	//成员函数重载+号
    	//Person operator+(Person& p1) {
    	//	Person temp;
    	//	temp.m_valueA = p1.m_valueA + this->m_valueA;
    	//	temp.m_valueB = p1.m_valueB + this->m_valueB;
    	//	return temp;
    	//}
    public:
    	int m_valueA;
    	int m_valueB;
    };
    //全局实现
    Person operator+(Person &p1, Person& p2) {
    	Person temp;
    	temp.m_valueA = p1.m_valueA + p2.m_valueA;
    	temp.m_valueB = p1.m_valueB + p2.m_valueB;
    	return temp;
    }
    //重载实现
    Person operator+(Person &p1, int num) {
    	Person temp;
    	temp.m_valueA = p1.m_valueA + num;
    	temp.m_valueB = p1.m_valueB + num;
    	return temp;
    }
    int main() {
    	Person p1,p2,p3;
    	p1.m_valueA = 10;
    	p1.m_valueB = 20;
    
    	p2.m_valueA = 10;
    	p2.m_valueB = 20;
    
    	p3 = p1 + p2;
    	cout << p3.m_valueA << '\n' << p3.m_valueB << endl;
        
        //重载
    	p4 = p1 +10;
    	cout << p4.m_valueA << '\n' << p4.m_valueB << endl;
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    image-20230920080946047

    4.5.2、左移运算符重载
    • 关键字operator<<
    #include
    using namespace std;
    class Person {
    	friend ostream& operator<<(ostream& cout, Person& p);
    public:
    	Person(int a, int b) {
    		m_A = a;
    		m_B = b;
    	}
    private:
    	int m_A;
    	int m_B;
    };
    
    //ostream 输出流类型
    ostream& operator<<(ostream &cout,Person &p) {
    	cout << "A:" << p.m_A << " B:" << p.m_B;
    	return cout;
    }
    
    void test07() {
    	Person p1(10,10);
    	cout << p1 << endl;;
    }
    int main() {
    	test07();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    4.5.3、后置运算符重载
    • operator++() 前置
    • operator++(int) 后置

    前置递增返回引用,后置递增返回值

    
    #include
    using namespace std;
    //重载递增
    
    class MyInterger {
    friend  ostream& operator<<(ostream& cout, MyInterger myint);
    public:
    	MyInterger() {
    		m_Num = 0;
    	}
    	//重载前置++运算符
    	MyInterger& operator++() {
    		m_Num++;
    		return *this;
    	}
    	//重载后置++运算符
    	//int 代表占位可以用于区别前置和后置
    	MyInterger operator++(int) {
    		//先记录当时结果
    		MyInterger temp = *this;
    		//后递增
    		m_Num++;
    		//最后将记录的结果做返回
    		return temp;
    	}
    private:
    	int m_Num;
    };
    //重载<<运算符
    ostream& operator<<(ostream& cout, MyInterger myint) {
    	cout << myint.m_Num << endl;
    	return cout;
    }
    void test06() {
    	MyInterger myint;
    	cout << ++myint << endl;
    }
    
    int main() {
    	test06();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    4.5.4、赋值运算符重载
    • operator=
    
    #include
    using namespace std;
    //赋值运算符重载
    class Person {
    public:
    	Person(int age) {
    		m_Age = new int(age);
    	}
    	~Person()
    	{
    		if (m_Age !=NULL)
    		{
    			delete m_Age;
    			m_Age = NULL;
    		}
    	}
    	//重载 赋值运算符
    	Person operator=(Person& p) {
    		//先判断是否有属性在堆区如果有先释放干净,然后再深拷贝
    		if (m_Age != NULL)
    		{
    			delete m_Age;
    			m_Age = NULL;
    		}
    
    		//深拷贝
    		m_Age = new int(*p.m_Age);
    
    		//返回本身
    		return *this;
    	}
    
    int* m_Age;
    };
    
    void test01() {
    	Person p1(18);
    	Person p2(20);
    	Person p3(40);
    	p3 = p2 = p1;
    	cout << *p1.m_Age << endl;
    	cout << *p2.m_Age << endl;
    }
    
    int main() {
    	test01();
    
    
    	system("pause");
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    4.5.5、关系运算符重载
    • operator==
    • operator!=
    #include
    #include
    using namespace std;
    class Person {
    public:
    	Person(string name, int age){
    		m_Name = name;
    		m_Age = age;
    	}
    	//重载等号
    	bool operator==(Person &p) {
    		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
    		{
    			return true;
    		}
    		return false;
    	}
    	//重载不等号
    	bool operator!=(Person &p) {
    		if (this->m_Name != p.m_Name && this->m_Age != p.m_Age)
    		{
    			return false;
    		}
    		return true;
    	}
    
    public:
    	string m_Name;
    	int m_Age;
    };
    void test01() {
    	Person p1("Tom", 18);
    	Person p2("Tom", 18);
    	Person p3("jeery", 18);
    	if (p1==p2)
    	{
    		cout << "P1和P2相等" << endl;	
    	}
    	else {
    		cout << "P1和P2不相等" << endl;
    	}
    
    	if (p1!=p3)
    	{
    		cout << "P1和P3不相等" << endl;	
    	}
    	else {
    		cout << "P1和P3相等" << endl;
    	}
    }
    
    int main() {
    	test01();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    4.5.6、函数调用运算符重载
    • 函数调用运算符()也可以重载
    • 由于重载后使用的方式非常像函数的调用,因此称为仿函数
    • 仿函数没有固定写法,非常灵活
    • operator()

    //匿名函数对象

    MyAdd()(100, 100)

    #include
    #include
    using namespace std;
    class MyPrint {
    public:
    	//重载函数调用运算符
    	void operator()(string test) {
    		cout << test << endl;	
    	}
    };
    void test01() {
    	MyPrint myPrint;
    	myPrint("hello world");//由于使用起来非常类似于函数调用,因此称为仿函数
    }
    //仿函数非常灵活,没有固定的写法
    //加法类
    
    class MyAdd {
    public:
    	int operator()(int num1, int num2) {
    		return num1 + num2;
    	}
    };
    
    void test02() {
    	MyAdd m1;
    	int sum  =  m1(100, 100);
    	cout << sum << endl;
    
    	//匿名函数对象
    	cout << MyAdd()(100, 100) << endl;
    }
    int main() {
    	//test01();
    	test02();
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
  • 相关阅读:
    springboot异常(一):springboot自定义全局异常处理
    记一次 .NET某上位视觉程序 离奇崩溃分析
    前端项目如何准确预估个人工时
    基于QT和C++实现的停车场管理系统
    [内存泄漏][PyTorch](create_graph=True)
    Java项目:ssm客户关系管理系统
    PMP备考大全:经典题库(敏捷管理第4期)
    中国DevOps平台市场,华为云再次位居领导者位置
    Vue 学习笔记 错误ResizeObserver loop completed with undelivered notifications
    车道线算法合集——各个车道线论文代码地址、算法总结、优劣分析
  • 原文地址:https://blog.csdn.net/NITIQ/article/details/133419455