C++对象模型初探
Posted 每天告诉自己要努力
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++对象模型初探相关的知识,希望对你有一定的参考价值。
- 成员变量和成员函数是分开存储的,意思就是说,成员函数严格来说不属于类的。C语言中的结构体是只存放变量,要在结构体之外声明定义实现函数。C++抽象出类的思想,把成员变量和成员函数封装在一个类中。C++在代码编写的时候看起来好像的确是把二者放在类内一起写,但是实际上成员函数跟成员变量是分开存储的。成员变量是属于类的,而成员函数则像静态成员函数一样是一块共享的数据,他们不属于类。因此当同一个类的不同对象访问同一个成员函数时,实际上是访问同一块地址空间。只有非静态成员变量才属于对象身上。
类内的数据和操作是分开存储的,并且每一个非内联成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共享一块代码。
- 空类的大小是1,因为类自己也可以作为一个被访问的“实例对象”(直接通过类名访问),因此就算是空类,内部也会维护一个char类型指针维护这个可以被当做实例访问的地址。这个大小为1的内存代表着这个类抽象出的实例的独有的地址(类似于准备出生的婴儿也要给他一个唯一的身份证),假如现在空类里面加入一个int型变量,则这个int型变量会找到这个大小为1的内存地址开始存储数据,所以会覆盖掉这个大小为1的内存。因此加入一个int变量之后,类的大小是4而不是5。
这里会引出内存对齐的概念,如果不修改对齐方式,则默认为内存对齐。内存对齐是什么意思呢?接着上面的例子,空类的大小为1,空位中加入了一个int型变量之后大小一共为4,那么此时在加入一个double变量之后,大小不是12,而是16。内存对齐就是把变量中内存最大的大小作为一个基准值,比如这里的double是8,基准值是8。空类中加入一个int之后,大小是4,对于8的基准值来说,一块基准值还剩下
(8- 4 )= 4
的大小,然而剩下的这4放不下一个double,因此需要去下一块基准值去存放这个double。加入了double之后,也就是16的大小了。这16的大小里面分为了两块基准值,第一块是int+4个空,第二块是存放double。以此类推……
- 前面说到对于一个类对象的成员函数是共用一块代码的,C++是通过提供特殊的对象指针(this指针)区分不同的对象。
this指针指向被调用的成员函数所属的对象。
#include <iostream>
using namespace std;
//创建一个人类
class Person {
public:
void func() {} //成员函数
string m_name;
int age;
};
int main () {
Person p1;
p1.func();//编译器回偷偷加入一个this指针 Person *this
//实际上执行的是 void func(Person *this),this此时代表p1这个对象,以此来区分是哪个对象访问该成员函数
return 0;
}
在上述代码中:当创建了一个p1对象之后,当p1调用成员函数func时,因为实际上func这个成员函数是只有一个,因此编译器会借助this指针偷偷传入p1对象,也就是 void func(Person *this),因为是p1调用的(p1.func();
),所以这个this是代表着p1这个对象。如果是其他对象调用func也是同理,虽然不同的对象都是共用一个func函数,但是依靠this指针可以让func区分是哪个对象在调用。
this指针是隐含在对象成员函数内的一种指针(构造拷贝析构函数中也有)。当一个对象被创建后,该对象的每一个成员函数都含有一个系统自动生成的隐含this指针,用以保存该对象的地址。也就是说虽然我们没有在成员函数的实现内写上this指针,但是编译器在编译时是会自动加上的。this指针称为“指向本对象的指针”,永远指向当前对象。
this指针是隐含在每一个类的非静态成员函数中的
,对象的非静态成员函数是不属于对象的,因此this指针当然也不属于对象,所以this指针不会影响sizeof(对象)
的结果。
this指针是C++实现封装的一种机制,它将对象和调用该对象的成员函数连接在一起。在外部看来,每一个对象都拥有自己的成员函数。一般情况下,并不用写this指针,系统会默认帮忙处理。
因为静态成员函数只有静态成员变量可以访问,而且静态成员函数是共享数据的,所以静态成员函数是没有this指针的。
- this指针指向对象本体,所以解引用之后就等于对象本体。
class Person {
public:
person(int age) {
this->age = age;
}
int age;
public:
Person& plusAge(Person &p) {
this->age += p.age;
return *this;
}
};
int main() {
Person p1(10);
Person p2(10);
p1.plusAge(p2).plusAge(p2);//链式编程思想,可以一直链接下去
//plusAge返回值是调用他的对象,所以p1.plusAge(p2)的返回值是加上p2年龄之后的p1对象,所以可以继续调用成员函数。
return 0;
}
注意:plusAge成员函数的返回值是引用,表明返回的是对象本身。如果把引用去掉,就是值拷贝的返回,也就是说会返回一个跟对象本身的值一样的
另一个不存在的对象
(编译器自动做了浅拷贝)。如果把引用去掉之后,第一次调用会给p1成功添加年龄,但是由于第一次调用成功之后返回的已经不是p1对象本身了,所以后面的链式编程已经不会对p1有影响。
成员函数的返回值是引用还是非引用,看业务需要来定。
- 空指针访问成员函数
class Person {
public:
Person (int age) {
this->m_age = age;
}
void show1() {
cout << "该函数没用到this指针,所以空指针也可以访问"<< endl;
}
void show2(int age) {
if (this == NULL) return;//用到了this指针,要做个判断
this->m_age = age;//相当于 NULL->m-age = age; 访问了不存在的地址。
cout << "该函数用到this指针,所以要做安全处理"<< endl;
}
int m_age;
};
int main () {
Person *p = NULL;
p->show1(); //可以成功访问,show1里面没用到this指针
p->show2(10); //用到了this指针,如果不加安全判断,则程序会崩溃
return 0;
}
①如果成员函数没有使用到this指针,则空指针可以方法该成员函数
②如果成员函数有使用到this指针,则空指针不能访问该成员函数(加上空判断防止该情况)
- 常函数和常对象
上面说的this指针,之所以this指针永远指向它的本体对象,
是因为this本质上是一个指针常量Person * const this
,指针的指向不能改变。
也就是说this指针绑定了本体对象,指向不能变,但是这个对象的变量可以变。
如果希望this指针指向的对象的变量也不要变,那么就要改成const Person * const this
既然this指针是编辑器自动会帮忙处理,那么我们就要在代码中给编译器一个提示,所以就引入了一个常函数的概念,就是在成员函数的后面加上const,那么当某个对象调用这个成员函数的时候,会自动为this指针加上const修饰,然后再传入成员函数。
**普通成员函数**
class Person {
public:
void showAge() {
//普通成员函数
this->m_age = 100;//该this指针是一个指针常量,永远指向p,但是p的成员属性可以改变。
}
int m_age;
};
int main() {
Person p;
p.showAge();//这里相当于是传入了 Person * const this 指针;
//p.showAge(Person * const this);
return 0;
}
**常函数**
class Person {
public:
void showAge() const {
this->m_age = 100;//该操作错误
//加上了const之后,showAge这个成员函数就变成了常函数,在常函数内不能修改成员的属性
this->m_mutable_age = 100;//加了mutable关键字之后可以修改
}
int m_age;
mutable int m_mutable_age;
};
int main() {
Person p;
p.showAge();//这里相当于是传入了 const Person * const this 指针;
//p.showAge(const Person * const this);
return 0;
}
**常对象,常对象只能调用常函数,因为常对象的成员属性是不能改变的。
但是普通的成员函数是有可能会修改成员属性,而常函数是无法修改成员属性的。
所以一般常对象跟常函数是搭配使用的。
const Person p;//在类名前加const,变成了常对象
p.m_age = 100; //错误操作,常对象的成员属性不能改变
p.m_mutable_age = 100;//正确操作,常对象的成员属性不能改变,除非该成员属性有mutable修饰
- 友元
全局函数、类、别的类的成员函数 ,三者可以做友元。
现在有一个A类,A类里面有私有的成员变量a
现在有一个B类,B类里面有一个成员函数b(公有、私有都可以)
现在还有一个全局函数C
如果
B类
想要访问A类的私有成员变量a,就要把B类
设置为A类的友元
如果B类里的成员函数b
想要访问A类的私有成员变量a,就要把B类的成员函数b
设置为A类的友元
如果全局函数C
想要访问A类的私有成员变量a,就要把全局函数C
设置为A类的友元
以上是关于C++对象模型初探的主要内容,如果未能解决你的问题,请参考以下文章
C++探索之旅第二部分第一课:面向对象初探,string的惊天内幕