C++对象模型和this指针
一、成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上
- 静态成员变量,静态成员函数都不属于;
- 非静态成员函数也不属于对象,因为只有一份;
举例:
【例子1】
class Person{};
void test_001()
{
Person p1;
//空对象占用的内存空间为:1
//C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置。
//每个空对象也应该有一个独一无二的内存地址。
cout << "size of p1 = " << sizeof(p1) << endl;
}
空对象p1
占用的内存空间为:1
C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置。
每个空对象也应该有一个独一无二的内存地址。
【例子2】
class Person
{
public:
int m_a; //非静态成员变量,属于类的对象上
static int m_b; //静态成员变量,不属于类的对象上
public:
void func() {} //非静态成员函数
};
int Person::m_b = 0;
void test_002()
{
Person p2;
cout << "size of p2 = " << sizeof(p2) << endl;
}
此时Person
类的对象p2
所占的内存空间为4(就是p2.m_a
的int
类型的4个字节)。除此之外,静态成员变量static int m_b
和非静态成员函数func()
都不属于类的对象,因此不属于p2
所占的内存空间。
二、this指针概念
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一段代码。那么这一块代码是如何区分哪个对象调用自己?this指针指向被调用的成员函数所属的对象
- this指针是隐含每一个非静态成员函数内的一种指针
- this指针不需要定义,直接使用即可
- this指针的用途:
(解决名称冲突) 当形参和成员变量同名时,可用this指针来区分
(返回对象本身用)在类的非静态成员函数中返回对象本身,可使用return* this
对于解决名称冲突的举例:
class Person
{
public:
int age;
public:
Person(int age)
{
//this指针指向被调用的成员函数所属的对象
age = age;
}
};
此时age
之间区分不开,编辑器会将Person(int age)
中的所有age
都视为是形参。
因此正确的做法应该是用this指针this->age
Person(int age)
{
//this指针指向被调用的成员函数所属的对象
this->age = age;
}
对于返回对象本身用的举例:
class Person
{
public:
int age;
public:
//用引用返回的是p2,否则是值传递,返回一个新的Person变量
Person& PersonAddAge(Person &p)
{
this->age += p.age;
return *this;//返回对象本身
}
};
void test_002()
{
Person p1(10);
Person p2(10);
//链式编程思想
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
cout << "p2的年龄 = " << p2.age << endl;
}
返回值用引用的方式则可以返回调用该函数的对象本身,通过Person& PersonAddAge()
可以返回对象p2
本身,从而实现链式编程思想。否则,如果是Person PersonAddAge()
,则p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
会变成p2
运行一次函数得到另一个新的对象(设为p2'
),然后p2'
再运行一次函数p2'.PersonAddAge(p1)
得到p2''
,然后p2''
再运行一次函数p2''.PersonAddAge(p1)
得到p2'''
,结束。
三、空指针访问成员函数
- 空指针也是可以调用成员函数的,但要注意有没有用到this指针。
void test01()
{
Person* p = NULL;
p->showClassName();
p->showPersonAge();
}
- 如果用到了this指针,需要加以判断保证代码的健壮性。
class Person
{
public:
int m_age;
public:
void showClassName()
{
cout << "this is Person class" << endl;
}
void showPersonAge()
{
//2.因此为了避免出现以下的错误,通常是写个条件
if (this == NULL)
{
return;
}
//1.报错的原因是传入的指针为NULL
cout << "age = " << m_age << endl; //因为此处的m_age具体是this->m_age
}
};
四、const修饰成员函数
常函数和常对象
常函数:
- 成员函数后加const
- 常函数内不可以修改成员属性
- 成员属性声明时加关键词mutable后,在常函数中依然可以修改
class Person
{
public:
int m_a;
public:
void showPerson() const
{
this->m_a = 100; //报错
}
void func()
{}
};
class Person
{
public:
mutable int m_b; //特殊变量,在常函数中也可以修改这个值
public:
void showPerson() const
{
this->m_b = 100; //加了mutable后,可以修改了
}
void func()
{}
};
分析:
this
指针本质是一个指针常量,即Person* const this
。意味着指向不可以修改,值可以修改。- 因此当成员函数后加了const后
void showPerson() const
,此时this
变成了const Person* const this
,在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改。 - 特殊变量
m_b
,在常函数中也可以修改这个值。
常对象:
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数
//常对象
void test02()
{
const Person p; //在对象前加const变为常对象
//p.m_a = 100; 常对象下不允许修改普通变量
p.m_b = 100; //m_b是特殊值,在常对象下也可以修改
//常对象只能调用常函数
p.showPerson();
//p.func(); 常对象不能调用普通成员函数,因为普通成员函数可以修改属性
}
分析:
Person p
前加了const变为常对象,因此不允许修改普通变量(普通成员属性)的值。- 但
m_b
是特殊值,在常对象下也可以修改 - 常对象
p
只能调用常函数p.showPerson()
,不能调用普通成员函数p.func()
,因为普通成员函数可以修改属性