在C++的学习中,可以把类当作一个模具,类所实例化出来的对象就是根据这个模具所产生的实体,对象可以看作是我们自己创建的一个新的数据类型。本文主要介绍了类对象通过拷贝函数进行初始化,分析了类对象的内存模型,以及通过this指针来实现更复杂的功能。最后介绍了析构函数的基础知识。

1、构造函数
首先,由于类只是一个模板,因此我们在定义类时无法对成员变量初始化,比如下面代码就是错误的:

class circle{
public:
int m_L = 20; // Error:不允许使用数据成员初始值设定项
};

因此,初始化只能发生在类创建对象的过程中,但是由于访问权限的原因,无法在类外访问某些成员变量,因此下面这种做法有时候是无效的:

circle C1; // 实例化一个对象 C1
C1.m_L = 20; // 通过创建的对象,来给对应变量初始化,但是如果m_L是private访问权限,则失效
为了解决这个问题,让程序员能像使用标准数据类型一样适用对象,在类内提供了一个特殊的成员函数——“构造函数”,专门用于在创建对象时初始化类对象。之所以说它特殊,是因为C++已经自动为构造函数提供了名称和使用语法,程序员只需要提供方法的定义即可,即:类名(形参列表)。具体来说,构造函数的定义如下:

class circle{
public:
int m_L;
public: //成员函数(方法)
circle(const int a) //通过构造函数对成员变量进行赋值
{
m_L = a;
}
};

circle C1(20); //调用格式
cout << “C1.m_L:” << C1.m_L <<endl;
看上去似乎很简单,但是由于构造函数也是函数,因此所有C++中的形参传递方式,函数特性以及函数调用方法都能用于构造函数。 如前文所讲,C++会自动给类添加一个空的构造函数,但是如果自己在类中实现了有参构造函数,编译器便不再提供无参构造函数。举例如下:

class circle{
public:
int m_L;
public: //成员函数(方法)
circle(const int a) //通过构造函数对成员变量进行赋值
{
m_L = a;
}
};

circle C1(20); //调用格式正确,能够通过构造函数赋值
circle C2; //错误,自己定义了有参构造函数,不再提供无参构造函数
构造函数可以重载,接着上面的例子,如果重载一个空的构造函数,那么两个调用格式都正确:

class circle{
public:
int m_L;
public: //成员函数(方法)
circle(){} // 空构造函数
circle(const int a) //通过构造函数对成员变量进行赋值
{
m_L = a;
}
};

circle C1(20); //调用格式正确,能够通过构造函数赋值
circle C2; //正确,可以通过重载的空构造函数实现初始化
构造函数的参数不仅可以是标准数据类型,也可以是类。众所周知,在数值作为函数参数进行传递的时候,会重新拷贝出来一份数据作为参数传递用完即销毁,这种方式不仅浪费了内存空间,而且无法修改原始数据。为了结合这两者之间的优点,于是经常采取引用作为函数的参数。虽然引用是指针的一种特殊情况,但是指针太过于灵活,并且引用在形式上引用与普通的变量地用法并没有什么区别,因此使用起来更加方便。如果不清楚引用和指针地区别,请移步上一篇文章:C++中的访问权限 - ZhiboZhao - 博客园 (cnblogs.com)

class circle{
public:
int m_L;
public: //成员函数(方法)
circle(){} // 空构造函数
circle(const int a) //通过构造函数对成员变量进行赋值
{
m_L = a;
}
//引用作为函数参数传递,并用const修饰,节省空间的同时避免修改原数据
circle(const circle& sub_circle)
{
m_L = sub_circle.m_L;
}
};

circle C1(20); //调用格式正确,能够通过构造函数赋值
circle C2(C1); //正确,可以通过拷贝构造函数进行初始化
2、C++类的内存模型
C++中,一个类包括:

成员变量:静态成员变量和普通成员变量
成员函数:静态成员函数和普通成员函数
虽然为了集成,我们将其写到一个类里面,但是只有普通成员变量真正属于类的对象,类的所有对象共享一份静态成员函数,静态成员变量和普通成员函数。结合前面的内容 内存四区之代码区,全局区,栈区和堆区 - ZhiboZhao - 博客园 (cnblogs.com),我大致画出了内存模型,如下图所示:

为了进一步理解,我们举例如下:

2.1 只定义成员函数
class person{
public:
// 定义一个空的构造函数
person(int m_age, int m_ID){
}
};
person p1(10, 20);
cout << “p1 所占的空间为:” << sizeof(p1) << endl;
输出结果为:

p1 所占的空间为:1
这个题目在《剑指offer》一书中也提到过,由空类实例化出来的对象所占的内存空间是1个而不是0个字节,因为编译其给对象 p1 分配了一个地址,来表示不同的对象存储在不同的地址空间,因此占用1个字节。

2.2 往空类中添加静态成员变量
class person{
static int age; //静态成员变量,存在全局区,不属于类对象的一部分
static int ID; //静态成员变量,存在全局区,不属于类对象的一部分
public:
// 定义一个空的构造函数
person(int m_age, int m_ID){
}
};
person p1(10, 20);
cout << “p1 所占的空间为:” << sizeof(p1) << endl;
输出结果为:

p1 所占的空间为:1
当向类中加入了成员函数与静态成员变量时,类的实例化对象仍然只占用1个字节的空间,足以证明这些函数和变量并不是类对象的一部分。

2.3 再加入非静态成员变量
class person
{
static int age; //静态成员变量,存在全局区,不属于类对象的一部分
static int ID; //静态成员变量,存在全局区,不属于类对象的一部分
int a; //非静态成员变量,存在栈区,属于类对象的一部分
public:
person(int m_age, int m_ID){
}
};