C++学习笔记(八)(C++对象模型和this指针 ,友元)
本笔记主要来源于教程https://www.bilibili.com/video/av41559729?p=1
4.3 C++对象模型和this指针
4.3.1 成员变量和成员函数分开储存
在C++中,类内的成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上
//成员变量和成员函数分开存储的 class Person { int m_A;//非静态成员变量 属于类的对象上 static int m_B;//静态成员变量 不属于类的对象上 void func()//非静态成员函数 不属于类的对象上 { } //int m_C; static void func2()//静态成员函数 不属于类的对象上 { } }; int Person::m_B = 0; void test01() { Person p; //空对象占用的内存空间为:1 //C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置 //每个空对象也应该有一个独一无二的内存地址 cout << "size of p= " << sizeof(p) << endl; } void test02() { Person p; cout << "size of p= " << sizeof(p) << endl; } int main() { //test01(); test02(); system("pause"); return 0; }
4.3.2 this指针概念
通过4.3.1我们知道在C++中成员变量和成员函数是分开存储的
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码。
那么问题是:这一块代码是如何区分哪个对象调用自己的呢?
C++通过提供特殊的对象指针,this指针,解决上述问题,this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可。
this指针的用途:
- 当形参和成员变量同名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用return *this
class Person { public: Person(int age) { //this指针指向被调用的成员函数所属的对象 this->age = age; } Person& PersonAddAge(Person &p)//如果不返回引用而返回值 Person,会调用拷贝函数,创建新对象,结果仍为20 { this->age += p.age; //this指向p2的指针,而*this指向的就是p2这个对象本体 return *this; } int age; }; //1、解决名称冲突 void test01() { Person p1(18); cout << "p1的年龄为: " << p1.age << endl; } //2、返回对象本身用*this void test02() { Person p1(10); Person p2(10); //链式编程思想 p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1); cout << "p2的年龄为: " << p2.age << endl; } int main() { //test01(); test02(); system("pause"); return 0; }
4.3.3 空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性。
//空指针调用成员函数 class Person { public: void showClassName() { cout << "this is Person class" << endl; } void showPersonAge() { //报错原因是因为传入的指针为NULL if (this == NULL) { return;//避免this的坑 提高代码健壮性 } cout << "age= " << m_Age << endl;//相当于this->m_Age } int m_Age; }; void test01() { Person *p = NULL; p->showClassName(); p->showPersonAge();//报错 } int main() { test01(); system("pause"); return 0; }
4.3.4 const修饰成员函数
常函数:
- 成员函数后加const我们称这个函数为常函数
- 常函数内不可以修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中仍然可以修改
常对象
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数
示例:
//常函数 class Person { public: //this指针的本质是 指针常量 指针的指向是不可以修改的 //Person *const this; //在成员函数后面加const,修饰的是this指针,让指针指向的值也不可以修改 const Person *const this; void showPerson() const { this->m_B = 100; //this->m_A = 100; //this = NULL;//this指针是不可以修改指向的 } void func() { m_A = 100; } int m_A; mutable int m_B;//特殊变量,即使在常函数中,也可以修改这个值,加关键字mutable }; void test01() { Person p; p.showPerson(); } //常对象 void test02() { const Person p;//在对象前加const,变为常对象 //p.m_A = 100;//报错 p.m_B = 100;//m_B是一个特殊值,在常对象下也可以修改 //常对象只能调用常函数 p.showPerson(); p.func();//报错,常对象不可以调用普通成员函数,因为普通成员函数可以修改属性 } int main() { system("pause"); return 0; }
4.4 友元
生活中你的家有客厅(Public),有你的卧室(Private)
客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去
但是呢,你也可以允许你的好闺蜜好基友进去
在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术
友元的目的就是让一个函数或者类,访问另一个类中私有成员
友元的关键字为friend
友元的三种实现:
- 全局函数做友元
- 类做友元
- 成员函数做友元
4.4.1 全局函数做友元
//建筑物类 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) { cout << "好基友全局函数 正在访问" << building->m_SittingRoom << endl; cout << "好基友全局函数 正在访问" << building->m_BedRoom<< endl; } void test01() { Building building; goodGay(&building); } int main() { test01(); system("pause"); return 0; }
4.4.2 类做友元
//类做友元 class Building; class GoodGay { public: GoodGay(); void visit();//参观函数,访问Building中的属性 Building *building; }; class Building { //GoodGay类是本类的好朋友,可以访问本类中私有的成员 friend class GoodGay; 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 test01() { GoodGay gg; gg.visit(); } int main() { test01(); system("pause"); return 0; }
成员函数做友元
class Building; class GoodGay { public: GoodGay(); void visit();//让visit函数可以访问Building中私有成员 void visit2();//让visit2函数不可以访问Building中私有成员 Building *building; }; class Building { //告诉编译器 GoodGay类下的visit成员函数作为本类的好朋友,可以访问私有成员 friend void GoodGay::visit(); public: Building(); public: string m_SettingRoom;//客厅 private: string m_BedRoom;//卧室 }; //类外实现成员函数 Building::Building() { m_SettingRoom = "客厅"; m_BedRoom = "卧室"; } GoodGay::GoodGay() { building = new Building; } void GoodGay::visit() { cout << "visit函数正在访问:" << building->m_SettingRoom << endl; cout << "visit函数正在访问:" << building->m_BedRoom << endl; } void GoodGay::visit2() { cout << "visit2函数正在访问:" << building->m_SettingRoom << endl; //cout << "visit2函数正在访问:" << building->m_BedRoom << endl; } void test01() { GoodGay gg; gg.visit(); gg.visit2(); } int main() { test01(); system("pause"); return 0; }
4.5 运算符重载
运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
4.5.1 加号运算符重载
作用:实现两个自定义数据类型相加的运算
//加号运算符重载 class Person { public: //1、成员函数重载+号 Person operator+(Person &p) { Person temp; temp.m_A = this->m_A + p.m_A; temp.m_B = this->m_B + p.m_B; return temp; } int m_A; int m_B; }; //2、全局函数重载+号 Person operator+(Person &p1, Person &p2) { Person temp; temp.m_A = p1.m_A + p2.m_A; temp.m_B = p1.m_B + p2.m_B; return temp; } //函数重载的版本 Person operator+(Person &p1, int num) { Person temp; temp.m_A = p1.m_A + num; temp.m_B = p1.m_B + num; return temp; } void test01() { Person p1; p1.m_A = 10; p1.m_B = 10; Person p2; p2.m_A = 10; p2.m_B = 10; //成员函数重载本质调用 Person p3 = p1.operator+(p2); //全局函数重载本质调用 Person p3 = operator+(p1, p2); Person p3 = p1 + p2; //运算符重载 也可以发生函数重载 Person p4 = p1 + 100;//Person+int cout << "p3.m_A= " << p3.m_A << endl; cout << "p3.m_B= " << p3.m_B<< endl; cout << "p4.m_A= " << p4.m_A << endl; cout << "p4.m_B= " << p4.m_B << endl; } int main() { test01(); system("pause"); return 0; }
总结1:对于内置的数据类型的表达式的运算符是不可能改变的 总结2:不要滥用运算符重载
4.5.2 左移运算符重载
作用:可以输出自定义数据类型
//左移运算符重载 class Person { public: Person(int a, int b) { m_A = a; m_B = b; } friend ostream &operator<<(ostream &cout, Person &p); private: //利用成员函数重载左移运算符 p.operator<<(cout) 简化版本p << cout //不会利用成员函数重载<<运算符,因为无法实现cout在左侧 /*void operator<<(Person &p) { } */ int m_A; int m_B; }; //只能利用全局函数来重载左移运算符 ostream &operator<<(ostream &cout, Person &p)//本质 operator<<(cout,p)简化cout << p { cout << "m_A= " << p.m_A << " m_B=" << p.m_B; return cout;//cout也可以改名字,因为引用的本身是起别名 } void test01() { Person p(10, 10); cout << p << endl; } int main() { test01(); system("pause"); return 0; }
总结:重载左移运算符配合友元可以实现输出自定义数据类型
4.5.3 递增运算符重载
作用:通过重载递增运算符,实现自己的整型数据
//重载递增运算符 //自定义整型 class MyInteger { friend ostream &operator<<(ostream&cout, MyInteger myint); public: MyInteger() { m_Num = 0; } //重载前置++运算符 返回引用是为了一直对一个数据进行递增操作 MyInteger& operator++()//如果不返回引用每次递增之后会创建一个新的变量 { m_Num++; return *this; } //重载后置++运算符 //void operator++(int) int代表占位参数,可以用于区分前置和后置递增 只能用int MyInteger operator++(int)//后置要返回值,因为返回的是临时变量执行结束之后会释放 { //先 记录当时结果 MyInteger temp = *this; //后 递增 m_Num++; //最后将记录结果做返回 return temp; } private: int m_Num; }; //重载左移运算符 ostream &operator<<(ostream&cout, MyInteger myint) { cout << myint.m_Num; return cout; } 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; }
4.5.4 赋值运算符重载
C++编译器至少给一个类添加四个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行拷贝
- 赋值运算符operator=,对属性进行拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
//赋值运算符重载 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) { //编译器是提供浅拷贝 //m_Age=p.m_Age; //应该先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝 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(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(); system("pause"); return 0; }
4.5.5 关系运算符重载
作用:重载关系运算符,可以让两个自定义类型对象进行对比操作。
//重载关系运算符 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; } string m_Name; int m_Age; }; void test01() { Person p1("Tom", 18); Person p2("Tom", 14); if (p1 == p2) { cout << " p1和p2是相等的! " << endl; } else { cout << " p1和p2是不相等的! " << endl; } if (p1 != p2) { cout << " p1和p2是不相等的! " << endl; } else { cout << " p1和p2是相等的! " << endl; } } int main() { test01(); system("pause"); return 0; }
4.5.6 函数调用运算符重载
- 函数调用运算符()也可以重载
- 由于重载后使用的方式非常像函数的调用,因此称为仿函数
- 仿函数没有固定写法,非常灵活。
//函数调用运算符重载 //打印输出类 class MyPrint { public: //重载函数调用运算符 void operator()(string test) { cout << test << endl; } }; void MyPrint02(string test) { cout << test << endl; } void test01() { MyPrint myPrint; myPrint("helloWorld");//由于使用起来非常类似于函数调用,因此称为仿函数 MyPrint02("helloWorld"); } //仿函数非常灵活,没有固定的写法 //加法类 class MyAdd { public: int operator()(int num1, int num2) { return num1 + num2; } }; void test02() { MyAdd myAdd; int ret=myAdd(100, 100); cout << "ret= " << ret << endl; //匿名函数对象 MyAdd()执行结束后立即被释放 类型+() cout << MyAdd()(100, 106 )<< endl; } int main() { test01(); test02(); system("pause"); return 0; }
匿名函数对象 MyAdd()执行结束后立即被释放 类型+()
最后欢迎大家访问我的个人博客青蛙听禅的博客
- 点赞
- 收藏
- 分享
- 文章举报
- C++学习笔记(六)-- 类和对象 构造函数和析构函数 const成员函数 this指针 对象数组 堆栈管理变量
- 《深度探索C++对象模型(Inside The C++ Object Model )》学习笔记
- C++学习笔记(13)——利用对象、引用、指针调用虚函数
- boolan——c++学习笔记之多态对象模型
- c++学习笔记 内存四区 函数调用模型 指针强化
- C++学习笔记(13)——利用对象、引用、指针调用虚函数
- 《深度探索C++对象模型(Inside The C++ Object Model )》学习笔记
- C++对象模型学习笔记
- c++对象模型笔记:指针类型转换
- C++对象模型学习笔记(二)--默认构造函数
- C++对象模型学习笔记(一)
- C++ 对象模型 学习笔记(2)
- C++对象模型学习笔记
- 深入C++对象模型学习笔记 第3章 Data 语意学
- 【C++学习笔记】this指针
- C++基础学习笔记----第十一课(类的静态成员、对象模型初步认识)
- 《深度探索C++对象模型(Inside The C++ Object Model )》学习笔记
- C++ 对象模型 学习笔记(1)
- c++ 学习笔记(47)-C++中两个类中互相包含对方对象的指针问题
- 深度探索C++对象模型学习笔记——Function语意学