类和对象下(this指针补充)+ 类和对象中构造函数和析构函数
一.this补充:
1.概念总结:
1.this 的作为成员函数的形参的时候满足 类的类型 * const this满足这样一个情况,说明this指针是不可以被修改的。
2.只能在成员函数的内部去使用,
3.this指针本质上是成员函数的一个隐藏的形参,通过外面的实参传给形参。实参其实是对象的地址。对象本身不回去存贮this。
4.this指针是成员函数的第一个隐藏的指针形参,通过ecx寄存器把地址值传过去的不需要程序员进行处理。
5.this指针在成员函数中是可以去使用的。
2.两个问题:
1.this指针存在哪里?
1-1:存在函数栈帧里面。
1-2:vs 2022: 通过ecx寄存器进行存贮:
2.this指针可以为空吗?this指针可以为空但是这个this指针作为成员函数的时候不可以去访问类的成员变量,只能访问成员函数中不需要访问成员变量的一些函数是不会出问题的。
二.构造函数和析构函数:
一.类的默认构造:
1.当一个类是空类的时候,会默认生成6个默认构造。
1.初始化和清理:
1-1:构造函数主要完成成员变量的初始化工作:
1-2:析构函数主要完成清理工作:
2.拷贝复制:
2-1:拷贝构造是使用同类对象初始化创建对象:
2-2:赋值重载主要是是把一个对象赋值给另一个对象。
3.取地址重载:
3-1:主要是普通对象和const对象取地址,这两个很少会自己实现:
二.构造函数:
1.概念:
构造函数是一个比较特殊的函数,他会在对象初始化的时候由编译器进行自动调用而且只会在一个对象的生命周期中只会去调用一次。
2.特征总结:
1.构造函数名称和类名称相同:
2.构造函数没有返回值:
3.构造函数在对象初始化的时候由编译器自己调用:
//我们的构造函数在这里进行了显示类型的构造:
4.构造函数是可以重载的:
4-1:函数的无参的构造函数和全缺省的初始化都是正常定义一个对象:
4-2:对象的初始化,显示的去传参数。
5.5. 如果类中没有进行显示的定义构造函数,那么C++编译器会默认生成一个无参的默认构造。
1.默认构造:
1.用户没有显示的构造函数,其他操作的什么都没有:生成随机值。
2.构造函数没有参数也是一种默认构造。
3.构造函数全部缺省也是一种默认构造:
4.总结:我们在进行对象初始化的时候不需要进行参数的传递,编译器就回去调用默认构造。
2.默认构造的作用:
2-1:表面
我们下面实现了一个栈这个类,如果我们使用默认构造调用的了默认构造函数进行初始化:
c
#include<iostream>
#include<stdlib.h>
#include<assert.h>
using namespace std;
class Stack
{
public:
//1.初始化:
Stack(int capacity_num = 5)
{
int* tmp = (int*)malloc(sizeof(int) * capacity_num);
if (tmp == nullptr)
{
perror("malloc file");
exit(-1);
}
arr = tmp;
sz = 0;
capacity = 5;
}
//2.判断栈空:
bool Empty()
{
assert(arr != nullptr);
if (sz == 0)
{
return true;
}
return false;
}
//3.压栈:
void Push(int x)
{
assert(arr != nullptr);
if (sz == capacity)
{
int* tmp = (int*)realloc(arr, sizeof(int) * (capacity * 2));
if (tmp == nullptr)
{
perror("realloc file");
exit(-1);
}
arr = tmp;
capacity *= 2;
}
arr[sz++] = x;
}
//4.获取栈顶元素:
int top()
{
assert(arr != nullptr);
if (!Empty())
{
return arr[sz];
}
}
//5.删除栈顶元素:
void del()
{
assert(arr != nullptr);
if (!Empty())
{
arr[sz--] = 0;
}
}
//6.打印栈中数据:
void print()
{
assert(arr != nullptr);
if (!Empty())
{
for (int i = 0; i < sz; i++)
{
cout << arr[i] << "->";
}
cout << endl;
}
}
private:
int* arr;
int sz;
int capacity;
};
int main()
{
Stack s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
s1.print();
s1.del();
s1.del();
s1.print();
return 0;
}
2-2:内涵
C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。
我们下面实现一个自己通过栈实现的队列:
2-2:(重要:)根据上面的图片我们知道,内置类型是不会去处理的,自定义类型会去调用他自己默认构造!
2-2:根据这个问题我们C++ 11 支持了变量在声明的时候给一个初始值一般是面对的是内置类型,因为内置类型不会去处理:!
c
//2.栈实现队列:
class Myqueue
{
public:
//1.入队列:
void my_push(int x)
{
push.Push(x);
sum++;
}
//2.出队列:
void my_pop()
{
if (!pop.Empty())
{
while (!push.Empty())
{
pop.Push(push.top());
push.del();
}
}
pop.del();
sum--;
}
//3.获取队头元素:
int get_top()
{
if (pop.Empty())
{
while (!push.Empty())
{
int tmp = push.top();
pop.Push(tmp);
push.del();
}
}
return pop.top();
}
//4.判断队列是否为空:
bool Eppty()
{
if (sum > 0)
{
return false;
}
return true;
}
private:
Stack push;
Stack pop;
//注意C++11 中针对内置类型成员不初始化的缺陷又打了一个补丁:
//内置类型成员变量在类中声明的时候可以给默认值:
int sum = 0;
};
int main()
{
Myqueue Q1;
return 0;
}
二.析构函数:
1.概念:
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
2.特征总结:
1.函数名和类名相同但是需要在前面加上一个~(按位取反)
2.没有参数没有返回类型:
对象生命周期结束时,C++编译系统系统自动调用析构函数
一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。
3.意义:
栈这样的类的析构才是有意义的:
c
#include<iostream>
#include<stdlib.h>
#include<assert.h>
using namespace std;
class Stack
{
public:
//1.初始化:
Stack(int capacity_num = 5)
{
int* tmp = (int*)malloc(sizeof(int) * capacity_num);
if (tmp == nullptr)
{
perror("malloc file");
exit(-1);
}
arr = tmp;
sz = 0;
capacity = 5;
}
//2.判断栈空:
bool Empty()
{
assert(arr != nullptr);
if (sz == 0)
{
return true;
}
return false;
}
//3.压栈:
void Push(int x)
{
assert(arr != nullptr);
if (sz == capacity)
{
int* tmp = (int*)realloc(arr, sizeof(int) * (capacity * 2));
if (tmp == nullptr)
{
perror("realloc file");
exit(-1);
}
arr = tmp;
capacity *= 2;
}
arr[sz++] = x;
}
//4.获取栈顶元素:
int top()
{
assert(arr != nullptr);
if (!Empty())
{
return arr[sz];
}
}
//5.删除栈顶元素:
void del()
{
assert(arr != nullptr);
if (!Empty())
{
arr[sz--] = 0;
}
}
//6.打印栈中数据:
void print()
{
assert(arr != nullptr);
if (!Empty())
{
for (int i = 0; i < sz; i++)
{
cout << arr[i] << "->";
}
cout << endl;
}
}
//7.栈中数据的清理:
~Stack()
{
free(arr);
arr = nullptr;
sz = 0;
capacity = 0;
}
private:
int* arr;
int sz;
int capacity;
};
//2.栈实现队列:
class Myqueue
{
public:
//1.入队列:
void my_push(int x)
{
push.Push(x);
sum++;
}
//2.出队列:
void my_pop()
{
if (!pop.Empty())
{
while (!push.Empty())
{
pop.Push(push.top());
push.del();
}
}
pop.del();
sum--;
}
//3.获取队头元素:
int get_top()
{
if (pop.Empty())
{
while (!push.Empty())
{
int tmp = push.top();
pop.Push(tmp);
push.del();
}
}
return pop.top();
}
//4.判断队列是否为空:
bool Eppty()
{
if (sum > 0)
{
return false;
}
return true;
}
private:
Stack push;
Stack pop;
//注意C++11 中针对内置类型成员不初始化的缺陷又打了一个补丁:
//内置类型成员变量在类中声明的时候可以给默认值:
int sum = 5;
};
int main()
{
Stack s1;
return 0;
}
1.通过观察下面的动图我们可以发现,在主函数栈帧结束以后我们的类对象开辟的空间被释放了!
2.我们观察一下我们实现队列的默认构造和默认析构: