本系列文章为黑马程序员C++教程学习笔记,前面的系列文章链接如下
C++核心编程:P1->程序的内存模型
C++核心编程:P2->引用
C++核心编程:P3->函数提高
C++核心编程:P4->类和对象----封装
C++核心编程:P5->类和对象----对象的初始化和清理
一、成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上。
我们先写一个空类,然后创建出一个对象,看看这个空对象占多大空间。
#include <iostream>
using namespace std;
#include <string>
class Person
{
};
void test01()
{
Person p;
cout << "size of p is: " << sizeof(p) << endl;
}
int main()
{
test01();
return 0;
}
运行,可以看到空对象大小为
1
1
1 字节。这是因为编译器为了区分空对象占内存的位置,给每个空对象也分配一个独一无二的内存地址。
当类中有成员变量时,看可能会占多大空间。我们这里在类中添加一个int类型的成员变量m_A。
#include <iostream>
using namespace std;
#include <string>
class Person
{
int m_A;
};
void test01()
{
Person p;
cout << "size of p is: " << sizeof(p) << endl;
}
int main()
{
test01();
return 0;
}
可以看到类对象的大小是4字节。
若包含了静态成员变量,则它不属于类对象,类型对象的大小不变
当我们添加静态和非静态成员函数时,类对象大小也不变,因为它们也不属于类对象上。
二、this指针
问题
在C++中成员变量和成员函数是分开存储的,每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码。那么问题是:这一块代码是如何区分那个对象调用自己的呢?
this指针
c++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象。this指针是隐含每一个非静态成员函数内的一种指针。this指针不需要定义,直接使用即可。
this指针的用途:
----当形参和成员变量同名时,可用this指针来区分
----在类的非静态成员函数中返回对象本身,可使用return *this
当形参和成员变量同名时,不能直接使用对应的名字来做修改,需要借助this指针
#include <iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
//1、当形参和成员变量同名时,可用this指针来区分
//this指针指向被调用的成员函数所属对象
this->age = age; //不能这样写 age = age
}
int age;
};
void test01()
{
Person p1(10);
cout << "p1.age = " << p1.age << endl;
}
int main() {
test01();
return 0;
}
运行,可以看出当成员变量和形参相同时,通过this指针修改了成员变量的值
现在我们想增加一个成员函数,实现将一个对象的年龄加到另一个对象的年龄上。
#include <iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
//1、当形参和成员变量同名时,可用this指针来区分
this->age = age;
}
void PersonAddPerson(Person &p)
{
this->age += p.age;
}
int age;
};
void test01()
{
Person p1(10);
cout << "p1.age = " << p1.age << endl;
Person p2(10);
p2.PersonAddPerson(p1);
cout << "p2.age = " << p2.age << endl;
}
int main() {
test01();
return 0;
}
运行,可以看出结果正确
但是如果现在我想在一行多次调用这个成员函数,发现会报错,该怎样修改呢?
解决方案:我们这里的成员函数没有返回值,所以肯定会报错。如果每次执行PersonAddAge都能返回当前对象,那么就可以继续调用PersonAddAge,所以可以作如下修改:
#include <iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
//1、当形参和成员变量同名时,可用this指针来区分
this->age = age;
}
//以引用的方式返回,返回对象本身
Person& PersonAddPerson(Person p)
{
this->age += p.age;
//返回对象本身
return *this;
}
int age;
};
void test01()
{
Person p1(10);
cout << "p1.age = " << p1.age << endl;
Person p2(10);
p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);
cout << "p2.age = " << p2.age << endl;
}
int main() {
test01();
return 0;
}
运行,可以看到成功多次调用了成员函数
注意:如果将PersonAddAge以值返回的方式,则结果会不对。这是因为每次返回的不是本体,而是重新创建了一个新的对象。然后下次就会修改这个新的对象的成员变量的值,最后导致最开始调用的那个对象只修改了一次。
#include <iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
//1、当形参和成员变量同名时,可用this指针来区分
this->age = age;
}
//以值的方式返回
Person PersonAddPerson(Person &p)
{
this->age += p.age;
//返回对象本身
return *this;
}
int age;
};
void test01()
{
Person p1(10);
cout << "p1.age = " << p1.age << endl;
Person p2(10);
//p2加一次后,返回p2的副本。
//第二次用p2的副本来加,返回p2副本的副本
//第三次用p2副本的副本来加,返回p2副本的副本的副本
//所以最后p2就加了1次
p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);
cout << "p2.age = " << p2.age << endl;
}
int main() {
test01();
return 0;
}
三、空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针。如果用到this指针,需要加以判断保证代码的健壮性
首先,我们确认空指针调用成员函数的情况
#include <iostream>
using namespace std;
//空指针访问成员函数
class Person {
public:
void ShowClassName() {
cout << "我是Person类!" << endl;
}
};
void test01()
{
Person * p = NULL;
p->ShowClassName(); //空指针,可以调用成员函数
}
int main() {
test01();
return 0;
}
运行,可以发现空指针也调用了成员函数
但是如果成员函数中用到了this指针,就不可以了
#include <iostream>
using namespace std;
//空指针访问成员函数
class Person {
public:
void ShowPerson() {
//属性的前面其实都默认加了个this,即等同于this->mAge
cout << mAge << endl;
}
public:
int mAge;
};
void test01()
{
Person * p = NULL;
p->ShowPerson(); //但是如果成员函数中用到了this指针,就不可以了
}
int main() {
test01();
return 0;
}
运行,可以发现报错,因为传入的指针是为NULL
解决方案:因此,我们在成员函数中用到this,就加入判断。如果是空指针,就直接return出来。
//空指针访问成员函数
class Person {
public:
void ShowClassName() {
cout << "我是Person类!" << endl;
}
void ShowPerson() {
if (this == NULL) {
return;
}
cout << mAge << endl;
}
public:
int mAge;
};
void test01()
{
Person * p = NULL;
p->ShowClassName(); //空指针,可以调用成员函数
p->ShowPerson(); //但是如果成员函数中用到了this指针,就不可以了
}
int main() {
test01();
system("pause");
return 0;
}
运行,可以发现代码不崩
四、const修饰成员函数
常函数:
①成员函数后加const后我们称为这个函数为常函数
②常函数内不可以修改成员属性
③成员属性声明时加关键字mutable后,在常函数中依然可以修改
我们先测试一下this指针的用途,可以看到this指针本质上就是个指针常量。
#include <iostream>
using namespace std;
class Person {
public:
//this指针的本质是一个指针常量Person* const this;,指针的指向不可修改
void ShowPerson(){
//this = NULL; //不能修改指针的指向
this->m_A = 100; //但是this指针指向的对象的数据是可以修改的
}
int m_A;
};
int main() {
Person p;
p.ShowPerson();
return 0;
}
运行,可以发现this可以修改指向的值,不能修改指向。
我们再来看下常函数和mutable的用处。可以看到常函数无法修改普通的成员变量,但是可以修改mutable修饰的成员变量。
#include <iostream>
using namespace std;
class Person {
public:
//this指针的本质是一个指针常量Person* const this,指针的指向不可修改,指向的值可以修改
//如果想让指针指向的值也不可以修改,需要声明常函数,在函数参数列表后加const
//这样this指针就变成了const Person* const this
void ShowPerson() const {
//this = NULL; //不能修改指针的指向 Person* const this;
//this->mA = 100; //现在this指针指向的对象的数据也无法修改了
this->m_B = 100; //mutable修饰的变量还是可以修改的
}
public:
int m_A; //无法修改
mutable int m_B; //可修改
};
int main() {
return 0;
}
常对象:
①声明对象前加const称该对象为常对象,也无法修改成员变量的值
②如果成员变量加了mutable,则就可以修改
③常对象只能调用常函数,因为普通函数可能会修改成员变量的值
我们看一下常对象的相关用法
#include <iostream>
using namespace std;
class Person {
public:
void MyFunc() const {}
void MyFunc2(){}
int m_A;
mutable int m_B; //可修改 可变的
};
//const修饰对象 常对象
void test01() {
const Person person; //常量对象
person.MyFunc(); //常对象只能调用常函数
person.m_B = 100; //常对象可以修改mutable修饰的成员变量
//person.MyFunc2(); //常对象不能调用普通成员函数
//p.m_A = 100; //常对象不能修改普通成员变量
}
int main() {
test01();
return 0;
}
运行,可以发现常对象只能调用常函数,且不能修改普通成员变量,但是可以修改mutable修饰的成员变量。