拷贝构造函数
1.拷贝构造函数,是一个特殊的构造函数。系统默认提供了。但是可以手动定义。
2.功能:使用一个类对象给另一个类对象初始化时,会自动调用拷贝构造函数
3.定义格式
bash
1、没有返回值
2、函数名与类同名
3、参数:该类的其他对象的引用
4、访问权限:一般为public
5、定义格式:类名 (类名 &other)
调用时机:
使用一个类对象给另一个初始化时:
string s1("hello world"); //调用有参构造
string s2 = s1; //调用拷贝构造
string s3(s1); //调用拷贝构造
使用值传递时实参取代形参时调用拷贝构造函数。
使用值返回时返回结果也是调用拷贝构造。
深浅拷贝问题:
如果类中有指针成员并且指向其它空间时,应调用深拷贝
使用浅拷贝就会出现多个对象中指针成员指向同一个内存空间,析构指针空间时,会造成对同一块内存空间的多次释放的情况,造成段错误(double free),此时就需要深拷贝。
深拷贝需要程序手动定义拷贝构造函数,在拷贝构造函数的初始化列表中,给新对象的指针成员重新分配空间,将原来对象指针空间指向的内容拷贝到该空间中即可完成深拷贝
cpp
Stu(Stu &other):name(other.name),age(other.age),score(other.score),ptr(new int(*other.ptr))
{
cout<<"Stu::深拷贝构造函数"<<endl;
}
移动构造函数
如果类中不显性定义拷贝构造函数或者移动构造函数,那么系统会自动提供这两个函数,如果程序员手动定义了其中任意一个函数,系统就两个都不提供了,如果需要使用,则需要程序员自己手动定义
移动构造函数,是将临时空间或将亡空间,托管给一个新的对象。
bash
1、没有返回值
2、函数名与类同名
3、参数:该类的其他对象的右值引用
4、访问权限:一般为public
5、定义格式:类名 (类名 &&other)
cpp
//自定义移动构造函数
Stu(Stu &&other):name(other.name),age(other.age),score(other.score),ptr(new int(*other.ptr))
{
cout<<"Stu::移动构造函数"<<endl;
}
//调用移动构造函数
Stu s3 = move(s1);
取地址运算符重载函数
功能:取得当前类对象的地址
定义格式
bash
1、返回值:当前类的指针
2、参数:无
3、权限:一般为public
4、格式: 类名 * operator&()
cpp
//自定义取地址运算符重载函数
Stu * operator&()
{
cout<<"取地址运算符重载函数"<<endl;
return this;
}
//取地址运算符重载函数
&s1;
一个空类中默认提供的特殊成员函数
cpp
class Temp
{
public:
Temp(){} //无参构造
Temp(const Temp &other){} //拷贝构造
~Temp(){} //析构函数
Temp * operator&(){return this;} //取地址运算符重载函数
Temp(const Temp &&other){} //移动构造
Temp &operator=(const Temp& other){} //拷贝赋值函数
Temp &operator=(const Temp&& other){} //移动赋值函
};
匿名对象
匿名对象就是没有名字的是对象,其生命周期就在调用的语句内,可以理解成一个将亡值。
定义时调用构造函数。
使用场景
1、使用匿名对象给一个新定义的对象进行初始化
2、使用匿名对象给数组进行初始化
3、使用匿名对象作为函数参数传递
cpp
Stu("lisi", 20); //定义一个匿名对象
cout<<"***********************************"<<endl;
//匿名对象使用方式1:给新的对象进行初始化工作
Stu s2 = Stu("wangwu", 30); //依赖移动构造
cout<<"***********************************"<<endl;
//匿名对象使用方式2:给对象数组进行初始化
Stu s[3] = {Stu("zhaoliu", 30), Stu("sunqi", 30), Stu("zhouri", 30)};
cout<<"***********************************"<<endl;
//匿名对象使用方式3:作为函数的形参进行传递
fun(Stu("wukong", 500));
cout<<"***********************************"<<endl;
友元
可以在其他类中访问当前类的所有信息,友元可以无条件访问该类中的所有权限下的成员。
友元分为友元函数和友元类
cpp
class Stu; //对类的前置声明
void fun(Stu s);
//类中声明一个全局函数作为友元函数
friend void fun(Stu s);
//定义一个全局函数
void fun(Stu s)
{
s.show(); //在类外可以正常调用类中的公共权限下的成员
cout<<"name = "<<s.name<<endl; //友元函数可以访问对应类中的所有权限下的成员
cout<<"age = "<<s.age<<endl; //友元函数可以访问对应类中的所有权限下的成员
}
Stu s1("zhangsan", 18);
fun(s1);//调用
cpp
#include <iostream>
using namespace std;
class Dog;
class Cat
{
private:
int count;
double weight;
public:
Cat(){cout<<"无参构造"<<endl;}
Cat (int c,double w):count(c),weight(w){cout<<"有参构造"<<endl;}
~Cat(){cout<<"析构函数"<<endl;}
friend void sum(Cat &c,Dog &d);
};
class Dog
{
private:
int count;
double weight;
public:
Dog(){cout<<"无参构造"<<endl;}
Dog (int c,double w):count(c),weight(w){cout<<"有参构造"<<endl;}
~Dog(){cout<<"析构函数"<<endl;}
friend void sum(Cat &c,Dog &d);
};
void sum(Cat &c,Dog &d)
{
cout<<"猫总个数"<<c.count<<endl;
cout<<"猫总重量"<<c.weight*c.count<<endl;
cout<<"狗总个数"<<d.count<<endl;
cout<<"狗总重量"<<d.weight*d.count<<endl;
}
int main ()
{
Cat cat (5,1.50);
Dog dog (6,2.00);
sum (cat,dog);
return 0;
}
友元类
声明一个A类为B类的友元类,则B允许A访问其所有权限下的成员包括私有成员
声明格式:friend 类名;
cpp
class teacher
{
//定义成员函数
void display(Stu s); //只能类内声明,类外定义
}
//声明老师类为友元类
friend class Teacher;
友元的总结
1> 不到万不得已的情况下,不要使用友元,因为友元的出现使得封装称为虚谈,友元破坏了类的封装性
2> 友元不具有传递性:A是B的朋友,B不一定是A的朋友
3> 友元不具有传递性:A是B的朋友,B是C的朋友,A不一定是C的朋友
4> 友元不具有继承性:父类的朋友,不一定是子类的朋友
5> 必须使用友元的情况:插入和提取运算符重载时,只能使用友元函数来解决
常成员(const)
引入常成员可以保护成员函数和成员变量、常对象
成员函数仅仅对成员变量有只读权限,不需要进行更改,那么此时就需要对成员函数进行const,达到保护成员变量的功能。
在传递某个对象时,对方仅仅只是对该对象有只读权限,也需要使用常成员,进一步保护成员变量
常成员函数
1> 定义格式:定义成员函数时,在括号后加关键字 const
返回值类型 函数名(形参列表) const
2> 作用:在常成员函数中,是不允许更改成员变量的值
3> 类中常成员函数与同名的非 常成员函数构成重载关系,原因是,形参this的类型不同
非常成员函数中形参this:类名 * const this;
常成员函数中的形参this: 类名 const * const this;
4> 非 常对象,优先调用非常成员函数,如果没有该非常成员函数,那么就会调用同名的常成员函数
常对象
1> 定义格式:const 类名 对象名;
2> 常对象只能调用常成员函数,不能调用非常成员函数
mutable关键字
1> 使用该关键字修饰的成员变量,可以解除常属性,即使在常成员函数中也能更改该变量的值
cpp
//定义成员函数
void show()const // 类名 const * const this;
常函数
1> 格式:const 返回值类型 函数名(形参列表){函数体内容}
2> 功能:常函数是保护函数的返回值不被修改的
cpp
//引用函数,const修饰的函数称为常函数
const int &fun()
{
static int num = 520;
return num;
}
关于C/C++中const的使用
1> const修饰普通变量,表示定义的是一个常变量,该变量只可读不可写,定义时必须初始化
2> const修饰指针变量时,如果放在*前表示,指针所执行空间中的内容可读不可写
cpp
const int *p = &data; // p 指向的是 const int, 所以 *p 里的内容不能修改但是可以改变指向
如果放在*后,表示指针的值可读不可写,也就是指针指向不能改变
cpp
int *const p = &data; // p 是 const 指针, 所以 p 不能更换指向
如果两边都加,表示都不可以更改,都是可读不可写,包含指向和值
3> const修饰函数的形参,表示传过来的数据,可读不可写
4> const修饰函数返回值时,表示保护函数的返回结果不被修改(指针函数和引用函数)
5> const修饰常成员函数,放到成员函数最后面,表示保护成员变量不被修改