目录
一,this指针
首先,我们先观察以下类:
#include <iostream>
using namespace std;
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1, d2;
d1.Init(2022, 1, 11);
d2.Init(2022, 1, 12);
d1.Print();
d2.Print();
return 0;
}
对于上述类,有这样的一个问题: Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调用 Init 函 数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢? C++中是通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(即此类),在函数体中所有“成员变量” 的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。如下图:
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//void Print(Data* this),错误,不能显示的写实参和形参,里面默认就有this指针
void Print()
{
//我们不能在形参中写,但是可以在类里面使用,因为默认有this指针
cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
这里需要说明以下几个问题:
1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
2. this指针只能在“成员函数”的内部使用。
3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给 this形参。所以对象中不存储this指针。
4. this指针是“成员函数”第一个隐含的指针形参,而形参跟函数一样存储在栈区中,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。
二,构造函数
介绍:
构造函数:在创建对象时,自动的进行初始化工作。语法:类名() {...}
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时,由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在对象整个生命周期内只调用一次。
特征:
1,构造函数的访问权限一般情况下要设置为public,设置为private将无法访问构造函数,导致建立类时自动访问将会出错。
2,函数名必须与类名相同。
3,没有返回值,也不写void。
4,可以有参数,可以重载,可以有默认参数。
5,创建对象时会自动调用一次,不能手工调用。
这里需注意以下几点:
1,如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明。
2,构造函数的权限若设置为private将会将会出错,因为权限不可访问。
3,如果定义了许多类,构造函数将会按照先后顺序调用。
默认构造函数:
如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类 型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,而编译器生成的默认构造函数只会对自定类型成员调用它的构造函数,内置类型一般不做处理,但有些编译器会对其进行处理(这里建议都看成不处理),即只处理自定义类型,为解决这一问题,在C++11标准中,内置类型成员变量在类中声明时可以给默认值。
内置类型是编译器自己定义的,所以一般都会自动给它们初始化,但自定义类型不知是如何实现的,所以不会处理,需要自己定义构造函数进行处理。
综上所述:在平常中,最好自己定义构造函数,默认构造函数可以看成自动生成随机值。
样例示范1:
#include <iostream>
using namespace std;
class Date
{
public://设置为公有权限,否则相当于建立时将会出错
// 1.无参构造函数
Date()
{}
// 2.带参构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1; //定义一:调用无参构造函数
Date d2(2015, 1, 1); //定义二:调用带参的构造函数
Date d3(); //定义三:此种定义是函数的声明,声明了d3函数,该函数无参,返回一个日期类型的对象
return 0;
}
样例示范2:
#include <iostream>
using namespace std;
class B
{
public:
B(int _b = 0) :b(_b) { } //默认调用此构造函数,因为此构造函数满足构造要求
int b;
};
class A
{
public:
A() :a(5){ } //在创造时,默认调用此构造函数,因为其它构造函数不满足构造要求
A(const B& tem) :x(tem) { }
int a;
B x; //创建A时自动调用B的构造函数
};
int main()
{
A a;
return 0;
}
总:对于自定义类型,构造函数与之是同体的,一旦编译到该存在的区域时将随创建自动调用,当没有与之匹配的构造函数时或存在多个与之匹配的构造函数会报错。
三,析构函数
析构函数:在销毁对象前,自动的完成清理工作,即在对象生命周期结束时将会清理空间资源。
特征:
1,析构函数的函数名是在类名前加上一个符号 ‘~’。
2,析构函数无参数且无返回值类型。
3,一个类中只能有一个析构函数,若没有自己定义,系统会自动生成一个默认析构函数。
4,在对象的生命周期结束时,系统将会自动调用析构函数。
注意:
1,由于析构函数没有参数,因此此函数不支持重载。
2,析构函数的权限设置跟构造函数的设置一样,一般都要设置为public。
3,类的析构函数调用一般按照构造函数调用的相反顺序进行调用,即倒序,但要注意static和全局变量的情况,存储静态区的将会在整个代码结束后才会调用。
4,默认析构函数跟默认构造函数一样,只对自定义类型成员处理,即自定义类型成员会去调用他的析构函数,而内置类型成员不做任何处理。
5,析构函数只能释放该对象本身所占用的空间,但对象内部所指向的内存空间或其它空间没有被释放,最终将会造成内存泄漏。
6,由第2条可知,如果类中没有申请资源时,析构函数可不写,直接使用编译器生成的默认析构函数即可。因为当对象的生命周期结束后空间就自动销毁了,也就还给操作系统了,但是如果在堆区中开辟了空间就需要在析构函数中自己完成清理工作。
样例示范一:
class Time
{
public:
//首先权限设置必须为public,否则结束时将不可调用,将会出错
//其次,类对象的成员中没有在堆区中占用空间,可不写析构函数
~Time()
{
cout << "~Time()" << endl;
}
private:
int _hour;
int _minute;
int _second;
};
样例示范二:
class Stack
{
public:
Stack(size_t capacity = 3)
{
_array = (int*)malloc(sizeof(int) * capacity);
_capacity = capacity;
_size = 0;
}
void Push(int data)
{
_array[_size] = data;
_size++;
}
//首先权限要设置为public
//动态开辟空间,需要在析构函数中释放动态空间
~Stack()//析构函数中不能有参数
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
int* _array;
int _capacity;
int _size;
};
四,析构与构造的调用
构造函数的调用:
当一个程序开始定义类时,程序就自动进入了此类中的构造函数阶段,构造完了之后系统就会自动往下运行,当又遇到一个类时,又将进入此类中的构造函数,也就是说当存在多个类定义时,系统会按照从前往后的顺序调用此类的构造函数。
析构函数的调用:
析构函数是在一个函数即将结束时才会开始调用,一般情况下,调用的顺序是跟析构函数的调用顺序相反,即倒序,但是,当存在类似于static类时的情况就很不一样,因为此种情况是存储在系统的静态空间中,而静态空间不会随着函数栈帧的销毁而销毁,只有当一整个程序完毕之后才会系统才会做回收。
构造函数与析构函数
C++中的构造函数与析构函数一般情况下是连用的,如果一个对象的构造函数没有被调用或没有调用完全(对象没有完全被构造),那么该对象的析构函数也通常不会被调用,因为这意味着该对象并没有被成功创建,但特殊情况需特殊分析,有些情况是例外,如静态成员和动态分配的对象。
上图中的构造函数的调用顺序为C,A,B,D。
代码演示:
#include <iostream>
using namespace std;
int i = 0;
class Data2
{
public:
Data2()
{
i++;
cout << "2构造中的i = " << i << endl;
}
void Print()
{
i++;
cout << "2类输出中的 i = " << i << endl;
}
~Data2()
{
i++;
cout << "2析构中的i = " << i << endl;
}
};
class Data1
{
public:
Data1()
{
i++;
cout << "1构造中的i = " << i << endl;
}
void Print(Data2 p)//类传入,当此函数结束时,会调用Data2类中的析构,即销毁形参中的类
{
i++;
cout << "1类输出中的 i = " << i << endl;
}
~Data1()
{
i++;
cout << "1析构中的i = " << i << endl;
}
};
int main()
{
Data1 a;//调用Data1中的构造
Data2 b;//调用Data2中的构造
a.Print(b);
cout << endl;
//当系统运算到此步时就要开始调用析构函数了,在析构函数中,先析构Data2类型,再析构Data1的类型
return 0;
}