• 黑马C++ 02 核心 4 ——类和对象_C++对象模型和this指针_友元


    01 C++对象模型和this指针

    1.1 成员变量和成员函数分开存储

    • C++中,类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上,其他都是独一份

    空对象

    • C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
    • 每个空对象也应该有个独一无二的内存地址
    • 空对象占用的内存空间为: 1字节
    #include
    using namespace std;
    
    // 成员变量 和 成员函数 分开存储
    // 只有非静态成员变量才属于类的对象上,其他都是独一份
    
    // 空对象
    class Person
    {
    };
    
    void test01()
    {
    	// C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
    	// 每个空对象也应该有个独一无二的内存地址
    	Person p;
    	// 空对象占用的内存空间为: 1
    	cout << "size of p=" << sizeof(p) << endl;
    }
    
    int main()
    {
    	test01();//1个字节
    
    	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

    非空对象

    • 非静态成员变量,属于类的对象上
    • 静态成员变量 —— 需要类内声明,类外初始化,不属于类的对象上
    • 非静态成员函数,不属于类的对象上
    • 静态成员函数,不属于类的对象上
    #include
    using namespace std;
    
    // 成员变量 和 成员函数 分开存储
    // 只有非静态成员变量才属于类的对象上,其他都是独一份
    
    class Person
    {
    	int m_A;// 非静态成员变量,属于类的对象上
    
    	static int m_B;// 静态成员变量 —— 需要类内声明,类外初始化,不属于类的对象上
    
    	void func() {} // 非静态成员函数,不属于类的对象上
    
    	static void func2() {}// 静态成员函数,不属于类的对象上
    };
    
    int Person::m_B = 10;
    
    void test01() // 空对象,class里面没有内容
    {
    	// C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
    	// 每个空对象也应该有个独一无二的内存地址
    	Person p;
    	// 空对象占用的内存空间为: 1
    	cout << "size of p = " << sizeof(p) << endl;
    }
    
    void test02()
    {
    	Person p1;// 此时不是空对象
    	cout << "size of p1 =" << sizeof(p1) << endl;// 4个字节
    }
    
    int main()
    {
    	test01();//1个字节
    	test02();//4个字节
    
    	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

    1.2 this指针(指向被调用的成员函数所属的对象 重难点)

    通过上面我们知道在C++中成员变量和成员函数是分开存储的,每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码

    问题是:这一块代码是如何区分那个对象调用自己的呢?就是说 假设只有一个函数,有很多的对象p1,p2,p3都在使用这个函数,在函数体内部如何区分是p1调的还是p2调的,修改属性

    • C++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象,就是p1调用成员函数,this就指向p1;p2调用成员函数,this就指向p2,以此类推,哪个对象调用成员函数,则this指向哪个对象。
    • this指针是隐含每一个非静态成员函数内的一种指针,this指针不需要定义,直接使用即可

    this指针的用途

    • 当形参和成员变量同名时,可用this指针来区分
    • 在类的非静态成员函数中返回对象本身,可使用return *this

    this指针的本质 是指针常量 指针的指向是不可以修改的

    解决冲突问题

    方法1:使用m_,区分和形参的名称

    public:
    	Person(int age)//这是形参age
    	{
    		m_age = age; // 成员member,后面需要
    	}
    	int m_age;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    方法2:使用this指针(推荐)

    • p1 在调用有参构造函数,所以this指向被调用的成员函数所属的对象
    public:
    	Person(int age)//这是形参age
    	{
    		//this指针指向 被调用成员函数 所属的对象
    		//this指的是成员属性age,后面是形参age
    		this->age = age;
    	}
    	int age;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    返回对象本身用*this

    Person& PersonAddAge2(Person &p)// 第2个Person&表示以引用的方式传入
    	{
    		this->age += p.age;// 将别人的年龄加到自身上面
    
    		// this指向p2的指针,*this指向的就是p2这个对象的本体
    		return *this;// 要返回本体,要用引用的方式返回,不能只用值返回Person
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Person&使用(重难点)

    • 如果开头Person&变成Person的话,会变成值传递,Person就会创建和本体不一样的新数据,调用拷贝构造函数(用值得方式返回,会复制一份新的数据,Person和*this自身是不一样的数据)。通过一次次链式传递,新生成不同于p2的新的对象,每次返回的都是新的对象。
    • 使用Person的话,就是拷贝调用新的数据,作为返回值。
    • 下面的写法不能使用
    Person PersonAddAge2(Person &p)// 第2个Person&表示以引用的方式传入
    	{
    		this->age += p.age;// 将别人的年龄加到自身上面
    
    		// this指向p2的指针,*this指向的就是p2这个对象的本体
    		return *this;// 要返回本体,要用引用的方式返回,不能只用值返回Person
    		// 如果开头Person&变成Person的话,就会变成值传递
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    1.3 空指针访问成员函数

    • C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针,成员函数
    • 如果用到this指针,需要加以判断保证代码的健壮性,使用下面程序
    if (this == NULL)
    		{
    			return;
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 属性的前面都默认加入了this指针,this是个空值,没有确定的对象,也就不能访问属性m_age,不然就是无中生有,会报错
    #include
    using namespace std;
    
    //空指针调用成员函数
    
    class Person
    {
    public:
    
    	void showClassName()
    	{
    		cout << "this is Person class" << endl;
    	}
    
    	void showPersonName()
    	{
    		// 报错的原因是传入的指针为空NULL,没法无中生有
    		if (this == NULL)
    		{
    			return;
    		}
    		// 属性的前面都默认加入了this指针,this是个空值,没有确定的对象
    		cout << "age = " << this->m_Age << endl;// 属性默认加this
    	}
    
    	int m_Age;
    };
    
    void test01()
    {
    	Person* p = NULL;
    	p->showClassName();
    	p->showPersonName();
    
    	Person p1;
    	p1.showClassName();
    	p1.showPersonName();
    }
    
    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

    1.4 const 修饰成员函数(常函数 常对象)

    this指针 —— 指针常量,不加const指向的值可以修改

    • this指针的本质是指针常量,指针的指向是不可以修改的,不加const指向的值可以修改
    • this指针不可以修改指针的指向,若要指针指向的值也不可以修改,函数后面加const
    • const Person * const this:前面const限制指针的值不可以修改,后面const限制指针的指向不可以修改
    • 在成员函数后面加const,修饰的是this指针,让指针指向的值也不可以修改

    常函数

    • 成员函数后加const后我们称为这个函数为常函数
    • 常函数内不可以修改成员属性
    • 成员属性声明时加关键字mutable后,在常函数和常对象中依然可以修改
    // 常函数 —— 成员函数后加const,常函数内不可以修改成员属性
    class Person
    {
    public:
    	// this指针的本质 是指针常量 指针的指向是不可以修改的
    
    	// const Person * const this
    	// 前面const限制指针的值不可以修改,后面const限制指针的指向不可以修改
    
    	// 在成员函数后面加const,修饰的是this指针,让指针指向的值也不可以修改
    	void showPerson() const		//加入const后,属性值就不可以修改
    	{
    		this->m_B = 100;
    		// this->m_A = 100;
    		// this = NULL;
    		//this指针不可以修改指针的指向,若要指针指向的值也不可以修改,函数后面加const
    	}
    
    	//普通函数
    	void func() {}
    
    	int m_A;
    	//特殊变量,即使在常函数和常对象中,也可以修改这个值。加上关键字mutable,m_B可以修改值
    	mutable int m_B;
    };
    
    • 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

    常对象

    常对象:

    • 声明对象前加const称该对象为常对象
    • 常对象只能调用常函数,不可以调用普通的成员函数,因为普通成员函数可以修改属性
    void test02()
    {
    	const Person p;// 在对象前加const,变为常对象
    	// p.m_A = 100;// 对象属性不可以修改(报错)
    	p.m_B = 100;// m_B是特殊值,在常对象下也可以修改(加上mutable)
    
    	// 常对象只能调用常函数
    	p.showPerson();
    	// p.func();// 常对象不可以调用普通的成员函数,因为普通成员函数可以修改属性
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    总程序

    #include
    using namespace std;
    
    // 常函数 —— 成员函数后加const,常函数内不可以修改成员属性
    class Person
    {
    public:
    	// this指针的本质 是指针常量 指针的指向是不可以修改的
    
    	// const Person * const this
    	// 前面const限制指针的值不可以修改,后面const限制指针的指向不可以修改
    
    	// 在成员函数后面加const,修饰的是this指针,让指针指向的值也不可以修改
    	void showPerson() const		//加入const后,属性值就不可以修改
    	{
    		this->m_B = 100;
    		// this->m_A = 100;
    		// this = NULL;
    		//this指针不可以修改指针的指向,若要指针指向的值也不可以修改,函数后面加const
    	}
    
    	//普通函数
    	void func() {}
    
    	int m_A;
    	//特殊变量,即使在常函数和常对象中,也可以修改这个值。加上关键字mutable,m_B可以修改值
    	mutable int m_B;
    };
    
    void test01()
    {
    	Person p;
    	p.showPerson();
    }
    
    //常对象
    void test02()
    {
    	const Person p;// 在对象前加const,变为常对象
    	// p.m_A = 100;// 对象属性不可以修改(报错)
    	p.m_B = 100;// m_B是特殊值,在常对象下也可以修改(加上mutable)
    
    	// 常对象只能调用常函数
    	p.showPerson();
    	// p.func();// 常对象不可以调用普通的成员函数,因为普通成员函数可以修改属性
    }
    
    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
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    02 友元(便于访问私有函数)

    生活中你的家有客厅(Public),有你的卧室(Private),客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去,但是你也可以允许你的好闺蜜好基友进去。
    在程序里,有些私有属性,想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术友元的目的就是让一个函数或者类 访问另一个类中私有成员,友元的关键字为 friend

    2.1 全局函数做友元

    #include
    #include
    using namespace std;
    
    // 建筑物类
    class Building
    {
    	// goodgay全局函数 是 Building好朋友,可以访问 Building 找那个私有成员
    	friend void goodGay(Building* building);
    
    public:
    	// 无参构造函数
    	Building()
    	{
    		// 赋初值
    		m_SittingRoom = "客厅";
    		m_BedRoom = "卧室";
    	}
    
    public:
    	string m_SittingRoom;// 客厅
    
    private:
    	string m_BedRoom;// 卧室
    };
    
    // 全局函数 - 好朋友 访问私有成员
    void goodGay(Building* building) // 传入指针Building(引用也可以)
    {
    	cout << "好基友的全局函数 正在访问:" << building->m_SittingRoom << endl;// 公共属性
    
    	cout << "好基友的全局函数 正在访问:" << building->m_BedRoom << endl;// 私有属性
    }
    
    void test01()
    {
    	Building building1;// 实例化Building对象
    	goodGay(&building1);// 以指针的形式(传入地址)将对象传入全局函数
    }
    
    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

    2.2 类做友元 —— 一个类可以访问另一个类的私有成员(难点!!!)

    • 类外构建成员函数
    class Building
    {
    public:
    	// 无参构造函数
    	Building();
    
    public:
    	string m_SittingRoom;// 客厅
    
    private:
    	string m_BedRoom;// 卧室
    };
    
    // 类外写成员函数
    Building::Building() // 类中的成员函数,Building下面的构造函数Building
    {
    	m_SittingRoom = "客厅";
    	m_BedRoom = "卧室";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    步骤:

    1. 创建goodGay对象,调用goodGay的构造函数
    2. 首先调用 类GoodGay 无参构造函数GoodGay()——成员函数 GoodGay::GoodGay(),使用new Builing 创建一个building对象
    3. 在创建该对象的同时还调用Building构造函数——Building::Building(),里面已经赋好初值了。
    4. 调用visit函数,可以访问building内部维护的m_settingRoom
    5. friend class goodGay:可以让goodGay访问Building里面的私有成员
    #include
    #include
    using namespace std;
    
    // 类做友元 —— 让一个类可以访问另一个类的私有成员
    
    class Building;// 创建building,告诉编译器一会会写这个类
    
    class GoodGay// 创建goodgay类
    {
    public:
    	// 无参构造函数
    	GoodGay();
    	// 参观函数  访问Building中的属性 公共和私有属性
    	void visit();
    
    private:
    	Building* building;// 指针类型
    };
    
    class Building
    {
    	// GoodGay类是本类的好朋友,可以访问本类的私有成员
    	friend class GoodGay;
    
    public: // 赋初值
    	// 无参构造函数
    	Building();
    
    public:
    	string m_SittingRoom;// 客厅
    
    private:
    	string m_BedRoom;// 卧室
    };
    
    // 类外写成员函数
    
    Building::Building() // 类中的成员函数,Building下面的构造函数Building,赋初值
    {
    	m_SittingRoom = "客厅";
    	m_BedRoom = "卧室";
    }
    
    GoodGay::GoodGay()
    {
    	// 创建建筑物对象
    	building = new Building;
    	// new表示在堆区创建出一个对象,new什么样的类型,就返回什么样的指针
    }
    
    void GoodGay::visit()// goodgay下面的函数
    {
    	cout << "好基友类正在访问:" << building->m_SittingRoom << endl;
    
    	cout << "好基友类正在访问:" << building->m_BedRoom << endl;
    }
    
    void test01()
    {
    	GoodGay gg;// 创建goodGay对象,调用goodGay的构造函数
    	gg.visit();
    }
    
    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
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    在这里插入图片描述

    2.3 成员函数做友元

    building1 = new Building;
    	// building指针等于new的Building对象
    	// 相当于在堆区创建一个Building对象,并且用指针维护这个建筑物对象
    
    • 1
    • 2
    • 3
    // 告诉编译器,GoodGay类下的visit1成员函数作为本类的好朋友,可以访问私有成员
    	friend void GoodGay::visit1();
    	// friend void GoodGay::visit2();// 没有这句的话visit2不可以访问私有成员,只能访问私有属性
    
    • 1
    • 2
    • 3
    #include
    #include
    using namespace std;
    
    class Building;// 首先声明Building类
    class GoodGay
    {
    public:
    	// 无参构造函数
    	GoodGay();
    
    	// 用类外实现函数
    	void visit1();// 让visit函数可以访问Building中私有成员
    	void visit2();// 让visit2函数不可以访问Building中私有成员
    
    	Building* building1;
    };
    
    class Building
    {
    	// 告诉编译器,GoodGay类下的visit1成员函数作为本类的好朋友,可以访问私有成员
    	friend void GoodGay::visit1();
    	// friend void GoodGay::visit2();// 没有这句的话visit2不可以访问私有成员
    
    public:
    	Building();// 构造函数的声明
    
    public:
    	string m_SittingRoom;// 客厅
    
    private:
    	string m_BedRoom;// 客厅
    };
    
    // 类外实现成员函数
    Building::Building()
    {
    	m_SittingRoom = "客厅";
    	m_BedRoom = "卧室";
    }
    
    GoodGay::GoodGay()// 类外实现
    {
    	building1 = new Building;
    	// building指针等于new的Building对象
    	// 相当于在堆区创建一个Building对象,并且用指针维护这个建筑物对象
    }
    
    void GoodGay::visit1()
    {
    	cout << "visit1 函数正在访问:" << building1->m_SittingRoom << endl;
    
    	cout << "visit1 函数正在访问:" << building1->m_BedRoom << endl;
    }
    
    void GoodGay::visit2()
    {
    	cout << "visit2 函数正在访问:" << building1->m_SittingRoom << endl;
    
    	// cout << "visit2函数正在访问:" << building1->m_BedRoom << endl;
    	// 成员函数不做友元,不能访问私有成员
    }
    
    void test01()
    {
    	GoodGay gg;
    	gg.visit1();
    	gg.visit2();
    }
    
    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
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    在这里插入图片描述

  • 相关阅读:
    【超详细断点级别讲解 SpringSecurity】项目实战:用户认证、用户授权
    Solidity智能合约开发 — 3.5-库合约
    C---链表
    Linux snap 软件包管理
    密度图计数
    Anchor-Based:Anchor-Free你很会打吗?Waymo实时目标检测新SOTA!
    JUC并发编程第四篇,Java中的各种锁之乐观锁和悲观锁、公平锁和非公平锁、可重入锁以及死锁基础
    YOLO目标检测——谢韦尔钢材缺陷检测数据集下载分享【含对应voc、coco和yolo三种格式标签】
    学校宿舍一键视频对讲
    Vue 项目部署到GitHub Pages
  • 原文地址:https://blog.csdn.net/qq_42731062/article/details/126566859