1.析构函数
析构函数和构造函数的功能恰好相反,析构函数不是必须让我们完成对对象本身的销毁,比如一个类里面只有内置类型(int...)的类型,我们就不用手动去实现一个析构函数,这时候编译器自己生成的析构函数已经可以帮助我们去释放资源了。但是如果是我们向内存申请的空间,就像c语言中链表的节点类似的,就需要我们去手动释放空间,手动完成析构函数。
1.析构函数的函数名是类名的前面加'~';
2.析构函数无参数无返回值;(和构造函数类似,也是不用加void等等的)
3.一个类里面只能有一个析构函数,如果我们没有显式定义,编译器也会自动生成析构函数。
4.对象生命周期结束的时候,编译器会自动调用析构函数。
5.析构函数在对自定义类型处理的方式和构造函数类似,我们不实现对自定义类型的析构,编译器会自动去调用自定义类型的析构函数。
6.析构函数的顺序按照栈的顺序来析构,也就是后进先出,后面定义的变量最前析构。
#include <iostream>
using namespace std;
class Stack
{
public:
//构造函数
Stack(int n = 4)
{
_a = (int*)malloc(sizeof(int));
if (_a == nullptr)
{
perror("malloc");
exit(1);
}
_capacity = n;
_size = 0;
}
//析构函数
~Stack()
{
cout << "Stack()" << endl;
free(_a);
_a = nullptr;
_size = _capacity = 0;
}
private:
int* _a;
int _capacity;
int _size;
};
class MyQueue
{
public:
//写了析构函数,他也会去调用自定义类型的析构函数
~MyQueue()
{
cout << "~MyQueue ";
}
private:
//两个自定义类型
Stack pushst;
Stack popst;
};
int main()
{
Stack st;
MyQueue mq;
return 0;
}
2.拷贝构造函数
如果一个构造函数的第一个参数是自身类型的引用,并且任何额外的参数都有默认值,那么这个构造函数也就是拷贝构造函数,所以拷贝构造函数是构造函数的特殊形式。
1.拷贝构造函数可以和构造函数构成重载函数。
2.拷贝构造函数的第一个传参必须是类型对象的引用。如果是类对象的传值,那么就会引起编译器报错。
3.拷贝构造函数也可以有多个参数,但是第一个参数必须是类型对象的引用,后面的参数也必须有缺省值。
4.自定义类型对象进行拷贝必须调用拷贝构造,所以自定义类型传值和传值返回都会调用拷贝构造完成。
5.如果我们没有显示实现拷贝构造,编译器也会自动生成拷贝构造函数,自动生成的拷贝构造函数是对内置类型成员变脸完成值拷贝(一个字节字节的拷贝),对自定义类型成员变量会调用它的拷贝构造函数。
6.如果一个类显示实现了析构并释放资源,那么它就需要显示写拷贝构造,否则就不需要。
7.传值返回会产生一个临时对象的调用拷贝构造,传引用返回,返回的是返回对象的引用,没有产生拷贝。但是如果返回对象是一个当前函数局部域的局部对象,函数结束的时候就会销毁,这时候使用传引用返回是有问题的,就相当于返回了一个空指针,一个已经销毁了的对象,所以我们在返回的时候一定要确保对象存在。
#include <iostream>
using namespace std;
class Date
{
public:
//默认构造函数
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//引用的拷贝构造函数
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//指针的拷贝构造函数
//因为我不想改变d的指向和d指向的值,所以我使用了两个const来修饰
Date(const Date* const d)
{
_year = d->_year;
_month = d->_month;
_day = d->_day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
Date& T()
{
Date tmp(2222, 2, 2);
return tmp;
}
int main()
{
Date a(2026, 2, 2);
Date b = a;
Date rb(a);
b.Print();
rb.Print();
Date c = &a;
Date rc(&a);
c.Print();
rc.Print();
Date d = T();
d.Print();
return 0;
}
//关于栈的拷贝构造的探索
#include <iostream>
using namespace std;
class Stack
{
public:
Stack(int n = 4)
{
_a = (int*)malloc(sizeof(int) * n);
if (_a == nullptr)
{
perror("malloc");
exit(1);
}
_capacity = n;
_top = 0;
}
Stack(const Stack& st)
{
//这里是拷贝构造,需要先_a开辟和st一样大的空间大小
//然后再拷贝
_a = (int*)malloc(sizeof(int) * st._capacity);
if (_a == nullptr)
{
perror("malloc");
exit(1);
}
memcpy(_a, st._a, sizeof(int) * st._top);
_top = st._top;
_capacity = st._capacity;
}
void Push(int x)
{
if (_top == _capacity)
{
int newcapacity = _capacity * 2;
int* tmp = (int*)realloc(_a, newcapacity * sizeof(int));
if (tmp == NULL)
{
perror("realloc fail");
exit(1);
}
_a = tmp;
_capacity = newcapacity;
}
_a[_top++] = x;
}
~Stack()
{
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
int* _a;
int _capacity;
int _top;
};
int main()
{
return 0;
}
关于函数指针的复习
函数指针和函数一样,返回值和传参必须是相同的。例如:void fun(); 返回值是void,函数名是fun,参数没有;我们可以先创建一个函数指针和上面函数的类型和传参是相同的,但是不知道指向的函数指针:void (*”名字“)();这个就是一个函数指针了,只不过是一个空的指针。
这就是创建一个函数指针的方法,所以 void (A::*sf)() = A::fun;这就是直接创建一个sf的函数指针指向的是A类域里面的fun函数。
typedef void (* pf)();这个意思就是我定义 void (*)() 这个类型的名字是pf。
//函数指针的复习
#include <iostream>
using namespace std;
//int operator+(int x, int y)
//{
// return x + y;
//}
class A
{
public:
void fun()
{
cout << "A::fun()" << endl;
}
};
//定义一个函数指针的名字是PF,作用的类域是A里面,是一个无参数无返回值的函数指针。
//但是不知道指向哪一个函数。
typedef void(A::* PF)();
int main()
{
void (A:: * pp)() = &A::fun;
//创建一个函数指针的变量pf,指向A类域里面的fun函数,所以pf就是A类域里面的fun函数的指针了。
//下面就可以解引用直接使用A类域里面的fun函数。
PF pf = &A::fun;
A obj;
(obj.*pf)();
(obj.*pp)();
return 0;
}
3.运算符重载
1.当运算符被用于类型的对象时,C++语言允许我们通过运算符重载的形式指定新的含义。C++规定类类型对象使用运算符时,必须转换成调用对应运算符重载,若没有用对应的运算符重载,则会编译报错。
2.运算符重载是具有特殊名字的函数,它的名字是由operator和后面要定义的运算符共同构成。和其他的函数一样,它也具有返回类型和参数列表以及函数体。
3.重载运算符函数的参数个数和该运算符作用的运算对象数量一样多。一元运算符有一个参数,二元运算符有两个参数,二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数。例如++,它的运算对象只有一个。
4.如果一个重载运算符函数是成员函数,则它的第一个运算对象默认传给隐式的this指针,所以运算符重载作为成员函数的时候,参数比运算对象少一个。
5.运算符重载以后,它的优先级和结核性与对应的内置类型的运算符保持一致。
6.不能创建内置类型运算符中没有的运算符。(比如……)
7.重载运算符至少有一个类型对象参数。
8.重载++运算符的时候,有前置++和后置++的区别,运算符重载函数名都是以operator来命名,为了更好的区分,在后置++重载的时候增加一个int形参,和前置++构成函数重载,方便区分。int perator++(int n);
9.有五个运算符不能重载:.* 、:: 、sizeof、?:、. 。
10.重载<<和>>,需要重载为全局函数,因为重载为成员函数的话,this指针会默认在第一个形参的位置,第一个形参位置是左操作对象,调用的时候就会是val.<<cout,不符合使用习惯和可读性。重载为全局函数把ostream/istream 放到第一个位置,第二个参数的位置放当前类型对象。
4.赋值运算符重载
赋值运算符重载是一个默认成员函数,用于完成两个已经存在的对象直接的拷贝复制,注意和拷贝构造区分,拷贝构造是用于一个对象拷贝初始化给另一个创建的对象的时候调用的。
1.赋值运算符重载是一个运算符重载,必须重载为成员函数。
2.参数和返回值建议写成引用传参,减少拷贝,和提高效率,有返回值可以支持连续赋值的场景。
3.我们没有显示实现的话,编译器会自动生成一个默认赋值运算符重载,默认赋值运算符重载的行为和默认构造函数类似,对内置类型成员变量会完成值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型回去调用它们的赋值重载函数。

1394

被折叠的 条评论
为什么被折叠?



