1.友元friend
所谓友元,是指类与函数或类与类之间的一种关系。
友元授权予某些函数或类允许他们直接访问本类的隐藏信息。
友元声明
friend 返回类型 函数名(参数列表);
friend 返回类型 类名::函数名(参数列表);
友元类声明:该类中所有成员函数都可以直接访问当前类的隐藏信息
friend class 类名;
例:
class Demo
{
punlic:
friend void print_demo(Demo d);//全局函数友元声明
private:
int x;
};
void print_demo(Demo d)
{
cout<<d.x<<endl;
}
说明:
1,友元声明可出现在类中的任意位置
2,在友元声明处,可直接定义该函数,但他不是类的成员函数(不建议这么做)
3,友元关系是单向的、友元关系是不可传递的
4,同类型的对象,互为友元
2.运算符重载
运算符重载,就是给已有运算符添加新功能,使得这些运算符能适用于类 类型的对象。
在C++中,运算符黑丝用函数实现的,所以,预算符的本质,就是函数重载。
运算符与运算符函数的对应关系:
1、运算符号对应函数名
预算符函数的名称 由 关键字 operator 加上 运算符号组成
例:
+ :对应的函数名 operator +
<:对应的函数名 operator <
2、操作数 对应函数参数
3、运算结果对应函数的返回值
4、运算符过程对应函数体
当运算符函数编写完成,一般是由编译器自动调用,也可以像普通函数一样的直接调用。
3、双目运算符
典型的双目运算符,如 + - * / == != > >= < <=
重载的方式有两种:
1、可以重载为全局函数,一般需要做友元声明,此时,运算符函数有两个参数
2、可以重载为类的非静态成员函数,不需要友元声明,此时,运算符函数只有一个参数(左操作对象用于调用运算符函数,右操作对象作为参数)
输入输出运算符 >> <<
IO流对象(cout/cin/cerr等),不支持拷贝,所以,在传参和返回时,只能使用他们的引用。
输入输出运算符,只能重载为全局函数。
输出运算符的一般形式:
ostream & operator <<(ostream &out, 类名 对象名);
或
ostream & operator<<(ostream & out ,cosnt 类名 & 引用名);
输出运算符的一般形式:
istream& operator>>(istream & in ,类名 & 引用);
(拷贝)复制运算符
对象的拷贝分两种:
拷贝构造函数,用一个已存在的对象,初始化一个新对象
拷贝赋值运算符,用一个已存在的对象,给另一个已存在的对象赋值
例:
Demo d1;
Demo d2=d1;//拷贝构造
d2=d1;//拷贝赋值
如果一个类中没有显示定义拷贝赋值运算符函数,编译器会自动生成(浅拷贝)
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。
如果 对象中管理资源,最后就会导致多个对象共享一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为有效,所以当继续对资源进行操作时,就会发生访问违规。
如果对象占用了系统资源,自动生成的赋值运算符 就可能出错 ,此时,需要自己控制对象的拷贝过程。
赋值运算符只能重载为类的 非静态成员函数
一般如下:
class 类名
{
public:
类名&operator=(const 类名& other);
};
4.单目运算符
自增运算符++ 可以重载为 全局函数,也可以重载为累的非静态成员函数。
前++的一般形式:
类名 & operator ++ (类名& 引用名);//全局函数
类名& operator++ ();//类的非静态成员函数
后++的一般形式:
类名 operator++(类名& 引用名, int); // 全局函数
类名 operator++(int); // 类的非静态成员函数。
说明:
后++函数中的int,称为占位符参数,只占位置,不参与运算,用于区分前后++
--与++类似。
5.特殊运算符
[]下标运算符 只读重载为类的非静态成员函数,可能有两个版本
class Demo
{
public:
Demo()
{
p = new int[10]{1,2,3,4,5};
}
int& operator[](int index) // Demo* const this
{
return p[index];
}
const int& operator[](int index) const // const Demo* const this
{
return p[index];
}
private:
int* p;
};
() 函数调用运算符
如果一个类重载了 函数调用运算符,则这种类型的对象,可称之为 函数对象(仿函数)。
函数对象的行为 类似于函数,可以调用!
例:
class Demo
{
public:
void operator()()
{
}
void operator()(int x)
{
}
void operator()(int x, int y)
{
}
};
Demo d;
d();
d(100);
d(100,200);
6、lamda 表达式
lambda 表达是表示的是一段可调用的代码单元,本质上是一个函数对象,称为匿名函数。
语法格式:
[捕获列表](参数列表)->返回类型{函数体}
说明:
参数列表
与普通函数的参数列表类似,但他不能设置默认值,如果不需要传参,可以空着,也可以省略()
返回类型:
与普通函数的返回类型,通常省略不写,由函数体中的return表达式来自动推导
如果自动推导出来的类型与期望的不一致,则手动显示指定即可。
函数体:
可以为空,但大括号不能省略。
捕获列表:
一般来说lambda表达式出现在某个函数内部,函数中可能有局部变量
如果lambda表达式的函数体需要使用 该表达式所在函数中的局部变量,则需要在[]中进行说明。
如果需要捕获局部变量的值,在[]中些变量名即可,也可以用‘=’来代表捕获所有局部变量的值
如果需要捕获局部变量的引用,在[]中写&变量名既可,也可以用'&'来代表捕获所有局部变量的引用
例:
void foo()
{
int a = 0;
[&]{
a++;
cout << "a = " << a << endl;
};
}
auto关键字说明:
在C++11之前,它的功能和C语言中的auto一样,表示自动变量,用于修饰局部变量。
从C++11起,他不在表示自动变量了,而是用来根据对象的值 自动推导对象的类型。
例:
auto a=1;
auto b=1.2;
7、关键字explicit
explicit关键字用于防止编译器使用构造函数或类型转换运算符做隐式类型转换。
例:
class Demo
{
public:
Demo(int x) {}
};
Demo d1(100);
Demo d2 = 200; // Demo d2 = Demo(200);
类型转换运算符:
类型转换运算符是一种特殊的成员函数,负责将一个类类型的对象转换成其他的类型
一般如下:
operator 类型名()const
说明:
这种函数不需要些返回类型,operator后面的类型名就是返回类型
参数列表必须为空,同时应加上const
这种函数只能是非静态的成员函数。
例:
class Demo
{
public:
// 构造函数
explicit Demo(int x) : x(x)
{
cout << "Demo(int)" << endl;
}
// 类型转换运算符
explicit operator int() const
{
return x;
}
private:
int x;
};
Demo d = 100; // error
8、对象的移动(c++11)
对象的移动和对象的拷贝类似,也分两种:
移动构造
移动复制
C++标准增加了对象的移动操作,用于移动对象的资源。
当使用移动操作 把一个对象的资源转移给另一个对象后,原对象就不在拥有资源。
为了实现移动操作,C++引入了右值引用。
右值引用:只能绑定到右值的引用
一般如下:
类型名&&引用名=右值;
如果一个构造函数的参数是本类类型的右值引用,则称为移动构造函数
如果一个赋值运算符函数的参数是本类类型的右值引用,则称为移动赋值运算符。
例:
class Demo
{
public:
Demo()
{
p = new char[100];
}
Demo(Demo&& other); // 移动构造
Demo& operator=(Demo&& other); // 移动赋值
~Demo()
{
delete [] p;
}
private:
char* p;
};
一般情况下,只有参数是右值引用时,才能触发移动操作。
所以,C++标准库中提供了一个专门的函数move,用于获取给定对象的右值引用。
例:
Demo d1;
Demo d2 = std::move(d1);// 调用移动构造函数
对象移动的典型示例:
void swap(String& s1, String& s2)
{
String t = std::move(s1);
s1 = std::move(s2);
s2 = std::move(t);
}
9、三个结论
1.只能重载已有运算符,不能创造新的运算符
2.有些运算符不能重载, 比如: . ?: :: sizeof .*
3.有些运算符只能重载为类的非静态成员函数,比如:= [] () ->