C/C++教程

C++对象模型初探

本文主要是介绍C++对象模型初探,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
  1. 成员变量和成员函数是分开存储的,意思就是说,成员函数严格来说不属于类的。C语言中的结构体是只存放变量,要在结构体之外声明定义实现函数。C++抽象出类的思想,把成员变量和成员函数封装在一个类中。C++在代码编写的时候看起来好像的确是把二者放在类内一起写,但是实际上成员函数跟成员变量是分开存储的。成员变量是属于类的,而成员函数则像静态成员函数一样是一块共享的数据,他们不属于类。因此当同一个类的不同对象访问同一个成员函数时,实际上是访问同一块地址空间。只有非静态成员变量才属于对象身上。

类内的数据和操作是分开存储的,并且每一个非内联成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共享一块代码。
在这里插入图片描述

  1. 空类的大小是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。以此类推……

  1. 前面说到对于一个类对象的成员函数是共用一块代码的,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指针的。

  1. 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有影响。
成员函数的返回值是引用还是非引用,看业务需要来定。

  1. 空指针访问成员函数
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指针,则空指针不能访问该成员函数(加上空判断防止该情况)

  1. 常函数和常对象

上面说的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修饰

这篇关于C++对象模型初探的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!