- 构造函数
- 析构函数
- 拷贝构造函数
- 赋值重载函数
- 取地址操作符重载
- const取地址操作符重载
c++有六个默认的成员函数。如果你没有给出,编译器会自己生成。什么时候要显示定义,什么时候用默认的看具体情况,一般来说取地址操作符重载和const取地址操作符重载都用默认的即可
一.构造函数
构造函数不是定义对象,而是初始化对象,构造函数并没有开辟空间,而是开辟空间之后进行初始化的作用。构造规则:对内置类型不做处理,对自定义类型调用其构造函数。
c++将类型分为:
内置类型:int,char,long,所有类型的指针(包括类类型)…
自定义类型:struct,class,union定义的类型
这种对内置类型和自定义类型的不公平对待是不合理的,所以在c++11给出了补丁,在成员变量处可以指定缺省参数
#include<iostream>
using namespace std;
class A
{
private:
int _b = 3; //缺省参数
};
int main()
{
//实例化对象时,编译器会自动调用构造函数,_b = 3
A aa;
return 0;
}
1.1构造函数的定义
构造函数是特殊的成员函数:
- 函数名和类名相同
- 构造函数无返回值
- 构造函数可以重载
- 在实例化对象时,编译器会自动调用,不用程序员调用
#include<iostream>
using namespace std;
class A
{
public:
A()
{
_b = 3;
}
private:
int _b;
};
int main()
{
//实例化对象时,编译器会自动调用构造函数
A aa;
return 0;
}
1.2默认构造函数
默认构造函数有三种:编译器默认给的构造函数,无参的构造函数,全缺省的构造函数。并且一个类中默认构造函数只能有一个,推荐定义全缺省的默认构造函数。
编译器默认给出的构造函数
当类中没有构造函数时,编译器会自动给出默认的构造函数,对内置类型不处理,对自定义类型调用其默认构造函数。
class A
{
private:
int _b;
};
无参的构造函数
无参的构造函数也是三大默认构造函数的一种
class A
{
public:
A()
{
_b = 3;
}
private:
int _b;
};
全缺省的构造函数
编译器将全缺省的构造函数看作默认构造函数的一种。
class A
{
public:
A(int b = 3)
{
_b = b;
}
private:
int _b;
};
- 编译器会将无参的,自动给出的,全缺省的看作默认构造函数
- 默认构造函数只能有一个,因为他们不能构成重载
- 如果显示定义了构造函数,编译器就不会给出。
- 一般建议,每个类都有一个默认构造函数
二.析构函数
析构函数功能与构造函数恰好相反,析构函数不是销毁对象空间,对象空间是由编译器来销毁的,在销毁对象空间前,编译器调用析构函数,完成对象中资源的清理。
- 析构函数对内置类型不处理
- 对自定义类型调用其析构函数
2.1析构函数的定义
析构函数是特殊的成员函数:
- ~类名是析构函数名
- 析构函数无参无返回值
- 析构函数不可重载
- 编译器自动调用
#include<iostream>
using namespace std;
class A
{
public:
~A() //析构函数
{
}
private:
int _b;
};
int main()
{
//实例化对象时,编译器会自动调用构造函数
A aa;
return 0; //出了作用域后,对象销毁,编译器自动调用析构函数
}
- 如果类中没有申请资源时,不需要显示定义析构函数,使用编译器给出的即可,但是有资源的申请时,必须定义析构函数,否则会发生内存泄漏,
三.拷贝构造函数
拷贝构造函数也是构造函数的一种重载形式。拷贝构造函数是用一个以定义的对象来初始化另一个对象,如果不写,编译器会自动给出,写了编译器不再给出。
对于编译器自动给出的拷贝构造函数规则:
- 对于内置类型,按字节拷贝,也就是浅拷贝或者叫值拷贝
- 对于自定义类型,调用其对应的拷贝构造函数
#include<iostream>
using namespace std;
class A
{
private:
int _b;
};
int main()
{
//实例化对象时,编译器会自动调用构造函数
A aa;
//以下两种都是拷贝构造函数
A bb(aa);
A cc = aa;
return 0;
}
3.1拷贝构造函数的定义
- 拷贝构造函数函数名是类名(构造函数的重载)
- 拷贝构造函数只有一个显式形参(已定义对象的引用,一般用const修饰)。如果传值的话,编译器会直接报错,因为会导致无穷递归。
对于函数传参来说:
- 内置类型会浅拷贝
- 自定义类型会调用其拷贝构造函数
所以,当拷贝构造函数传参为传值时,就会无穷递归调用拷贝构造函数。
#include<iostream>
using namespace std;
class A
{
public:
A(const A& a)
{
_b = a._b;
}
private:
int _b;
};
int main()
{
//实例化对象时,编译器会自动调用构造函数
A aa;
//以下两种都是拷贝构造函数
A bb(aa);
A cc = aa;
return 0;
}
- 当一个类显式定义了析构函数时(即:涉及了资源的申请),我们就需要显示的定义拷贝构造函数。
四.赋值重载函数
函数重载的目的是使用相同的函数名调用功能相似的函数。运算符重载的目的是为了增强代码的可读性,可以让自定义类似使用运算符。
4.1运算符重载
函数原型:返回值类型 operator运算符(参数列表)
- 重载的操作符必有一个为自定义类型
- 参数列表中:第一个参数为左操作数,第二个参数为右操作数,当重载函数为成员函数时,第一个操作符为隐含的this指针
- .* sizeof :: ?:: . 这五个运算符不能重载
运算符重载函数可以是全局函数,也可以是成员函数。当运算符重载函数是全局函数时,有时候需要访问私有的成员变量,此时有几种方法:
- 将私有成员改为公有
- 新增成员函数来读取私有成员函数的值
- 将该函数设置为友元函数
#include<iostream>
using namespace std;
class A
{
//friend bool operator==(const A& aa, const A& bb);
public:
A(int b = 3)
{
_b = b;
}
//成员函数
bool operator==(const A& aa)
{
return _b == aa._b;
}
private:
int _b;
};
//全局函数
//bool operator==(const A& aa, const A& bb)
//{
// return aa._b == bb._b;
//}
int main()
{
A aa(4);
A bb(4);
//可以通过函数名调用
aa.operator==(bb);
//也可以用运算符调用
aa == bb; //这种会被编译器转化为 aa.operator==(bb)去call函数地址
}
4.2赋值重载
赋值重载是默认成员函数的一种,当不显示定义时,编译器会自动给出。对于编译器自动给出的和拷贝构造函数一样的规则:
- 内置类型浅拷贝
- 自定义类型调用其赋值重载
赋值重载的定义
- 参数:const 类名&
- 返回值类型:类名&, 有返回值是为了连续赋值,例如:a = b = c;
- 需要检测是否自己给自己赋值,可以增加效率
- 赋值重载必须是成员函数,因为如果是全局函数的话,由于类内没有赋值重载,编译器自己会生成一个,这样两个重载就会冲突。
#include<iostream>
using namespace std;
class A
{
public:
A(int b = 3)
{
_b = b;
}
A& operator=(const A& aa)
{
//防止自己给自己赋值发生
if (this != &aa)
_b = aa._b;
return *this;
}
private:
int _b;
};
int main()
{
A aa(4);
A bb(3);
//注意区分下面的两种:哪种是赋值重载,哪种是拷贝构造
//这是拷贝构造,而不是赋值重载,因为赋值重载是两个已经存在的对象,进行赋值,而cc还在定义
A cc = bb;
//这是赋值重载,编译器会将它转化为bb.operator=(aa);
bb = aa;
return 0;
}
什么时候用赋值重载呢?
和拷贝构造相同:涉及到资源申请就需要显示定义赋值重载。
4.3前置++,后置++重载
#include<iostream>
using namespace std;
class A
{
public:
A(int b = 3)
{
_b = b;
}
//前置++
A& operator++()
{
_b += 1;
return *this;
}
//后置++
//编译器为了区分前置和后置,在参数列表新增了一个标识int,起到占位作用
A operator++(int) //这里int只是标记,不可改为double等等
{
A tmp(*this);
_b += 1;
return tmp;
}
private:
int _b;
};
int main()
{
A aa(1);
//后置++,以下两种都可以,这里的0只是占位,1,2...都可以
aa.operator++(0);
aa++; //编译器会自动转化为aa.operator++(0)
//前置++,以下两种都可以
aa.operator++();
++aa; //编译器会自动转化aa.operator();
return 0;
}
五.取地址重载
这个默认成员函数一般不用重新定义,使用系统给出的即可
#include<iostream>
using namespace std;
class A
{
public:
A(int b = 3)
{
_b = b;
}
A* operator&()
{
return this;
}
private:
int _b;
};
六.const取地址重载
6.1const成员函数
将const修饰的成员函数叫做const成员函数,const修饰成员函数实际上修饰的是this指针,只是编译器规定了this指针不能显示的给出,所以规定了一个位置,当const出现在这个位置就是修饰的this指针,表明该成员函数中不能对类的成员修改。
#include<iostream>
using namespace std;
class A
{
public:
A(int b = 3)
{
_b = b;
}
void Print() //A& this
{
cout << _b << endl;
}
//这两个Print可以同时出现,因为构成了函数重载的条件,this指针的类型不同
void Print() const //const A& this
{
cout << _b << endl;
}
private:
int _b;
};
int main()
{
const A aa;
aa.Print();
return 0;
}
取地址重载和const取地址重载一般不用显示定义,用编译器默认给出的即可。