友元
友元的设置是因为这样就可以访问类中的private成员
设置某一个类或者是函数为友元的,用friend关键字声明友元
友元的三种形式:普通函数、成员函数、友元类
普通函数:在类内部声明然后再类外部定义。
class Point{
public:
friend float distance(const Point & rhs, const Point & rhs1);
private:
int _ix;
int _iy;
};
float distance(const Point & rhs, const Point & rhs1){
return (rhs._ix - rhs1._ix );
}
成员函数:另一个类的成员函数访问该类的私有成员
Point设置为前向声明,这样line中可以知道有Point,但是不知有_ix,所以说将函数声明和实现拆开,实现放在这两个对象的后面。
【注意】一个friend只能声明一个友元函数,所以可以将friend放在前面
当需要申明的友元函数非常多的时候每次都要在这个类中设置友元,同时又在原类中声明在类下面进行实现很麻烦,所以说这样直接将一个类申明为另外一个类的友元类
//友元类的实现就是
class Point;
class Line{
函数声明;
};
class Point{
friend class Line;
};
函数定义;
友元的特点
友元:使得使用类内部的成员使用更加灵活。
但是破坏了类的封装性。
友元是单向的,如果说A类是B类的友元类,A可以访问B的私有成员。
class B{
friend class A;
};
友元不具备传递性。
友元不能被继承。
运算符重载
这样的话就可以和自定义类型进行一样的操作,非常的方便。
不能重载的运算符:有5个
可以进行重载的运算符有42个
【了解】sizeof int 可以这样写
1234点运算符和sizeof是不能重载的
运算符重载的规则以及形式:
1. 操作数据的类型必须要有自定义类型或者枚举类型(如果都是int那么1+1 = 0乱套了)
2. 优先级和结合性固定不变的
3.操作符的个数是保持不变的,运算符重载的时候不能设置默认参数,这样的话操作数的个数就发生了变化
4. &&与||不再具有短路求值特性,不能造一个根本就不存在的运算符
运算符重载的形式有三种:
- 采用友元函数的重载形式
- 采用普通函数的重载形式
- 采用成员函数的重载形式
友元函数实现的重载形式
这样就可以借助于friend关系来访问私有元素,比第二种更加安全,只能在这个函数才能访问私有元素,不像是通过类中外开放接口。
//友元的方式实现运算符重载
class Complex{
//...
friend Complex operator+(const Complex & lhs, const Complex & rhs);
//...
};
//友元函数中的普通函数形式实现重载
Complex operator+(const Complex & lhs, const Complex & rhs){
//...
}
void test0(){
Complex cx(1,2);
Complex cx2(3,4);
Complex cx3 = cx + cx2; //看上去和内置类型的计算一样了
//Complex cx3 = operator(cx,cx2); //本质上是调用了operator+函数
}
普通的函数实现的重载形式
对于私有成员的保护不够,因为在外面直接使用getReal和getImage就可以获取私有元素
class Complex {
public:
//...
double getReal() const { return _real; }
double getImage() const { return _image; }
//...
};
Complex operator+(const Complex & lhs, const Complex & rhs)
{
return Complex(lhs.getReal() + rhs.getReal(),
lhs.getImage() + rhs.getImage());
}
void test0()
{
Complex c1(1, 2), c2(3, 4);
Complex c3 = c1 + c2;//ok
}
还可以通过成员函数来实现
class Complex{
public:
//...
Complex operator+(const Complex & rhs)
{
return Complex(_real + rhs._real, _image + rhs._image);
}
};
【注意】在类中实现的时候就需要注意只是需要传递一个对象就可以,因为本身还有一个this指针指向的对象。
像加号这一类不会修改操作数的值的运算符,倾向于采用友元函数的方式重载。参数中会标记const,这样就不能对于函数中的数
p3 = p1 + p2
//如果是成员函数
p3 = p1.operator+(p2);
//友元函数
p3 = operator+(p1,p2);
进行修改。如果是定义在类内部,那样的话就可以直接访问并且修改私有成员的内容。
【注意】友元函数和成员函数的表面都是+,本质上有区别
+=运算符
对于需要修改最终的对象的时候倾向于采用成员函数的方式实现。
重载形式的选择(重点)
- 不会修改操作数的值的运算符,倾向于采用友元函数的方式重载
- 会修改操作数的值的运算符,倾向于采用成员函数(有this提供就不写该对象)的方式重载
赋值=、下标[ ]、调用()、成员访问->、成员指针访问->* 运算符必须是成员函数形式重载
考虑函数的返回值,尽量和内置类型对象保持一致
【了解】在类内部只有=赋值运算符函数是类是默认提供的,其他的赋值运算符函数是不会默认提供的
++运算符重载
难点在于有前置++和后置++之分,借助于函数参数区别虽然不需要参数但是在后置++的参数中写int就可以
前置++是返回修改以后的对象,使用引用。参数是无参的。
后置++是返回修改修改以前的副本,返回副本。参数设置为int,只是为了区分。
//前置++的形式
Complex& operator++(){
cout << "Complex & operator++()" << endl;
++_real;
++_image;
return *this;
}
//后置++的形式
//参数列表中要多加一个int
//与前置形式进行区分
Complex operator++(int){
cout << "Complex operator++(int)" << endl;
Complex tmp(*this);
++_real;
++_image;
return tmp;
}
[]运算符
class CharArray{
public:
CharArray(const char * pstr)
: _capacity(strlen(pstr) + 1)
, _data(new char[_capacity]())
{
strcpy(_data,pstr);
}
~CharArray(){
if(_data){
delete [] _data;
_data = nullptr;
}
}
//"hello"来创建
//capacity = 6
//下标只能取到 4
char & operator[](size_t idx){
if(idx < _capacity - 1){
return _data[idx];
}else{
cout << "out of range" << endl;
static char nullchar = '\0';
return nullchar;
}
}
void print() const{
cout << _data << endl;
}
private:
size_t _capacity;
char * _data;
};
【注意】前面的const是不能修改指向,后面的那个const是不能修改其中的内容,但是如果是存的是一个指针的话,那么是不能修改这个指针指向,但是其中的内容也是可以进行改动的,如果不想让内容后序改动只能初始化的话,那么就在private那个指针的前面加上const
【注意】只需要提供一个idx就可以,因为会有this指针已经指向这个对象了
输入输出流重载
输出是一个cout(ostream对象),因为cout(cin)是不能进行复制的所以使用引用,同理形参的位置也是使用引用
cout和cin不能复制是因为在内核代码中对于拷贝构造函数是删除的。
成员函数因为隐含的this指针是在第一个,所以只能写成很奇怪的方式
p1 << cout;
所以说只能是使用友元函数的方式
class Point {
public:
//...
friend ostream & operator<<(ostream & os, const Point & rhs);
private:
int _x;
int _y;
};
ostream & operator<<(ostream & os, const Point & rhs)
{
os << "(" << rhs._x << "," << rhs._y << ")";
return os;
}
void test0(){
Point pt(1,2);
cout << pt << endl; //本质形式: operator<<(cout,pt) << endl;
}
输入流
仍然是用友元函数的方式实现
cin的返回对象仍然是cin本身,需要修改对象本身传入一个引用
class Complex {
public:
//...
friend istream & operator>>(istream & is, Complex & rhs);
private:
int _real;
int _image;
};
istream & operator>>(istream & is, Complex & rhs)
{
is >> rhs._real;
is >> rhs._image;
return is;
}
成员访问运算符(->这个可以重载,但是.不可以重载)
本节的例子感觉有点奇特,要求明白意思。
class Data
{
public:
Data(){}
~Data(){}
int getData() const{ return _data; }
private:
int _data = 10;
};
class MiddleLayer
{
public:
MiddleLayer(Data * p)
: _pdata(p)
{}
~MiddleLayer(){
if(_pdata){
delete _pdata;
_pdata = nullptr;
}
}
private:
Data * _pdata;
};
要求使用MiddleLayer ml;来实现Data * _pdata的功能。理由是因为M中只有一个指针Data * _pdata,要求ml对象实现完成指针的所有功能。
为什么需要进行封装:将一个栈上的指针封装成一个对象的时候,因为栈上的对象被销毁的时候,会主动调用析构函数,可以回收堆上的空间。
Data * p = new Data();
p->getData();
(*p).getData();
delete p;
p = nullptr;
//要求可以实现
ml->getData();
(*ml).getData();
ml = nullptr;
如果使用下面的方式进行初始化的时候,不能进行delete pdata操作,会出现double free问题。因为如果使用了delete pdata;但是M中的指针依然是指向这个位置,当M销毁的销毁的时候调用析构函数清理堆上空间的时候就出问题了。
如果是让pdata单独管理一块堆上的空间的话,就不会出现问题。
重载类型没得选,只能成员函数形式(特定规定->)。
返回类型是Data 类型的指针。
在getData()前面会自动添加->指针,即使是已经对于->指针进行了重载。
智能指针的最大的效果就是自动回收。
要返回一个引用,这样的话不是对于副本进行操作,而是对于本身。
Data & operator *(){
return *_pdata;
}
额外再加一层:
Data *p = new Data();
MiddleLayer ml(p);
ThirdLayer tl(ml);
上述这种会出现tl管理栈上对象的效果,这样的话当tl(最后被调用)先销毁的时候销毁了栈上的空间,当程序结束的时候程序销毁栈上空间的时候出现问题。
想法就是将这个栈上的空间放到堆上去,同时借鉴于避免浅拷贝的问题所以都是使用匿名
ThirdLayer tl(new MiddleLayer(new data));
在构造的时候先构造thL然后Ml然后是data构造函数。
析构函数的调用也是同样的顺序。
这个地方在thl对象的->返回的是ml对象,ml的又会调用->获取到的data *pdata然后就是正常的->
【注意】->只能是写一次,下面的*可以一次次的调用,也可以一次调用
【理解】这个地方需要注意->和*函数和返回值是一样的,因为都是需要返回一个middleLayer对象,然后借助于middleLayer对象的运算符重载进行处理。
//*可以多次调用
cout << (*(*tl)).getData() << endl;
//*的实现直接一步到位的使用
*tl.getData();
//一步到位的实现
//方式一:借助于跨层的实现,*pml得到ml对象,*ml得到data
Data & operator*(){
return *(*pml);
}
//方式二:借助于友元函数的实现,因为ml中的_pdata是一个私有元素
//所以在ml类中设置TL为友元类,这样的话就可以在tl中访问_pdata指针
Data & operator*(){
return *(*pml)._pdata;
}
//在函数的中调用方式都是这样
cout << (*tl).getData() << endl;
//但是两者的本质是不同的
//方式一:借助于跨层重载
cout << (tl.operator*()).operator*().getData() << endl;
//方式二:借助于友元类
cout << tl.operator*().getData() << endl;
可调用实体
普通函数,函数指针,成员函数指针,类中的成员函数,函数对象都被称为可调用实体。
函数对象
重载了函数调用运算符的类的对象称为函数对象,可以像对象一样使用,也可以像函数一样使用
背景需求:希望对象像函数一样被调用。查看函数被调用多少次,如果是直接定义全局变量不安全,如果是定义局部静态变量出了大括号就不能随便访问了。所以说一个想法是将这个计数器放在类中的私有区域中,通过一个接口在访问访问次数。
所以说:函数对象可以携带函数的状态(函数被调用的次数)。
对于运算符()进行重载
class FunctionObject{
public:
void operator()(){
cout << "FunctionObject operator()()" << endl;
++ _count;
}
int operator()(int x, int y){
cout <<"operator()(int,int)" << endl;
++ _count;
return x + y;
}
int _count = 0;//携带状态
};
void test0(){
FunctionObject fo;
cout << fo() << endl;
cout << fo.operator()() << endl;//本质
cout << fo(5,6) << endl;
cout << fo.operator()(5,6) << endl;//本质
cout << "fo._count:" << fo._count << endl;//记录这个函数对象被调用的次数
}
函数指针
void print(int x){
cout << "print:" << x << endl;
}
void display(int x){
cout << "display:" << x << endl;
}
int main(void){
//省略形式
void (*p)(int) = print;
p(4);
p = display;
p(9);
void (*)(int) p = print;//error
//完整形式
void (*p2)(int) = &print;
(*p2)(4);
p2 = &display;
(*p2)(9);
}
p和p2可以抽象出一个函数指针类型void(*)(int) —— 逻辑类型,不能在代码中直接以这种形式写出
使用typedef定义:
Function类的“对象”可以这样使用,这个类的“对象”都是特定类型的函数指针,只能指向一种函数(这种函数的类型在定义函数指针类时就决定了)
typedef void(*Function)(int);
Function f;
f = print;
f(19);
f = display;
f(27);
成员函数指针
定义一个成员函数信息,确定属于哪一个类,函数返回类型,函数信息
非静态类的函数
typedef void (*Function)(int); //定义函数指针类型
typedef void (FFF::*MemberFunction)(int); //定义成员函数指针类型
.*是成员指针运算符的一种形式
//只能这么写,定义时需要使用完整形式即&即取地址
//还需要指明类的命名,因为在成员函数在存储的时候也会有类的标记
void (FFF::*p)(int) = &FFF::print;
FFF ff;
(ff.*p)(4);
//使用的时候进行解引用
//通过对象调用
//需要解引用,和定义配套的使用。同时加上*也不会从这个类中找相应的函数
//又优先级比较高所以加()
//.*是配套的成员指针运算符一种形式
->*成员指针运算符的一种形式
FFF * fp = new FFF();
(fp->*mf)(65);//通过指针调用成员函数指针
函数指针的意义:就是将函数视为一个变量存在,也可以将函数指针作为参数传给别的函数。
成员函数也是同样的效果,就是记得加上类名。
【注意】注意一个问题,一个空指针也可以调用得到结果
现象:空指针没有指向有效的对象。对于不涉及数据成员的成员函数,不需要实际的对象上下文,因此就算是空指针也可以调用成功。对于涉及数据成员的成员函数,空指针无法提供有效的对象上下文,因此导致错误。
原因:因为在查找的时候就直接从程序代码区就可以找到相应的类名对应的成员函数,不会用到这个对象,但是当访问对象中的成员的时候,需要借助于这个对象指针。
类型转换函数
以前又int向double这种转换,现在讨论自定义类对象向系统类型转换,以及转换回来。
以前见到过类似的隐式转换
因为默认参数:Point p = 1;
string str = "hello";将const char *转换为string类型
class Point{
public:
//将返回类型写在中间
operator int(){
cout << "operator int()" << endl;
return _ix + _iy;
}
//...
};
自定义类型转换内置类型
Point pt(3,4);
pt = 1;
一般是先进行隐式转换,发现1可以通过默认参数构造成一个point,然后再调用到赋值运算符函数
如果将默认构造函数的隐式转换关掉,那么就出错。
这个时候可以修改赋值运算符函数传入一个int数据得到一个point对象
num = pt使用类型转换函数,不然只能去int去修改int的函数。
自定义类型向自定义类型进行转换
多种方式
1.可以在一个类中实现类型转换
class Complex
{
//...
operator Point(){
cout << "operator Complex()" << endl;
return Point(_real,_image);
}
};
2.也可以进行隐式转换
注意前向声明(Point可以知道又com),声明为friend(com中可以访问),以后后来的定义
3.也可以使用赋值运算符修改的方式
如果当前2条内容都存在的时候那么会使用类型转换函数,而不是使用隐式转换函数,不要系统自己隐式转换了。类型转换的优先级高于隐式转换。
如果当这些3条内容都存在的时候那么会使用赋值运算符函数,因为赋值直接就可以使用,不用再进行类型转换。
string类型的+=
其实const函数和非const函数其中的内容是一样的,但是必须要再定义一遍,因为const只能使用const函数。
const函数的形式
const char & operator[](String &s) const{}前面的const 用来限制不能修改这一片区域的内容,后面的这个const是用来限制只是被const对象来使用。
输入流的实现
耍流氓做法一
//TODO这个地方也就是耍流氓,只能动态扩容,勉强用vector实现
istream &operator>>(std::istream &is, String &s){
string word;
is >> word;
if(s._pstr){
delete [] s._pstr;
s._pstr = new char[word.size() + 1]();
strcpy(s._pstr, word.c_str());
}
return is;
}
耍流氓做法二
//TODO这个地方也就是耍流氓,只能动态扩容,勉强用vector实现
istream &operator>>(std::istream &is, String &s){
vector<char> vec;
char ch;
while((ch = is.get()) != '\n'){
vec.push_back(ch);
}
s._pstr = new char[vec.size() + 1]();
strncpy(s._pstr, &vec[0], vec.size());
return is;
/*
string word;
is >> word;
if(s._pstr){
delete [] s._pstr;
s._pstr = new char[word.size() + 1]();
strcpy(s._pstr, word.c_str());
}
return is;
*/
}
string的函数重载
//string.hpp
#include <iostream>
using namespace std;
class String
{
public:
String();
String(const char *);
String(const String &);
~String();
String &operator=(const String &);
String &operator=(const char *);
String &operator+=(const String &);
String &operator+=(const char *);
char &operator[](std::size_t index);
const char &operator[](std::size_t index) const;
std::size_t size() const;
const char* c_str() const;
friend bool operator==(const String &, const String &);
friend bool operator!=(const String &, const String &);
friend bool operator<(const String &, const String &);
friend bool operator>(const String &, const String &);
friend bool operator<=(const String &, const String &);
friend bool operator>=(const String &, const String &);
friend std::ostream &operator<<(std::ostream &os, const String &s);
friend std::istream &operator>>(std::istream &is, String &s);
private:
char * _pstr;
};
String operator+(const String &, const String &);
String operator+(const String &, const char *);
String operator+(const char *, const String &);
//string.cc
#include "string.hpp"
#include <string.h>
String::String()
:_pstr(new char[1]())
{}
String::String(const char * pstr)
:_pstr(new char[strlen(pstr) + 1]())
{
strcpy(_pstr, pstr);
}
String::String(const String &pstr)
:_pstr(new char[pstr.size() + 1]())
{
strcpy(_pstr, pstr.c_str());
}
String::~String(){
if(_pstr){
delete [] _pstr;
_pstr = nullptr;
}
}
String & String::operator=(const String &pstr){
if(&pstr != this){
//REM 判断的是指针!!
if(_pstr) delete [] _pstr;
_pstr = new char[strlen(pstr._pstr) + 1]();
strcpy(_pstr, pstr._pstr);
}
return *this;
}
String &String::operator=(const char *pstr){
if(_pstr) delete [] _pstr;
_pstr = new char[strlen(pstr) + 1]();
strcpy(_pstr, pstr);
return *this;
}
String & String::operator+=(const String & rhs){
//这样的话了解底层空间的分配问题
//程序块结束的时候会回收指针但是不会回收堆上的空间
char *temp = new char[strlen(_pstr) + strlen(rhs._pstr)+ 1]();
strcpy(temp, _pstr);
strcat(temp, rhs._pstr);
delete [] _pstr;
_pstr = temp;
return *this;
/*
char *temp = new char[strlen(_pstr) + 1]();
strcpy(temp, _pstr);
delete [] _pstr;
if(this != &rhs) _pstr = new char[strlen(temp) + strlen(rhs._pstr) + 1]();
else _pstr = new char[strlen(temp) + strlen(temp) + 1]();
strcpy(_pstr, temp);
if(this != &rhs) strcat(_pstr, rhs._pstr);
else strcpy(_pstr, temp);
delete [] temp;
return *this;
*/
}
String &String::operator+=(const char * pstr){
char *temp = new char[strlen(_pstr) + 1]();
strcpy(temp, _pstr);
delete [] _pstr;
_pstr = new char[strlen(temp) + strlen(pstr) + 1]();
strcpy(_pstr, temp);
strcat(_pstr, pstr);
delete [] temp;
return *this;
}
char &String::operator[](std::size_t index){
return _pstr[index];
}
const char & String::operator[](std::size_t index) const{
return _pstr[index];
}
size_t String::size() const{
return strlen(_pstr);
}
const char* String::c_str() const{
return _pstr;
}
bool operator==(const String &lhs, const String &rhs){
return !strcmp(lhs._pstr, rhs._pstr);
}
bool operator!=(const String &lhs, const String &rhs){
return strcmp(lhs._pstr, rhs._pstr);
}
bool operator<(const String & lhs, const String & rhs){
for(size_t idx = 0; idx < lhs.size(); idx ++){
//只有相等才能继续往后比较
if((lhs._pstr[idx] - rhs._pstr[idx] < 0 )){
return true;
}else if(lhs._pstr[idx] - rhs._pstr[idx] > 0 ){
return false;
}
}
if(rhs.size() > lhs.size()) return true;
return false;
}
bool operator>(const String &lhs, const String &rhs){
if(lhs == rhs) return false;
if(lhs < rhs) return false;
return true;
}
bool operator<=(const String &lhs, const String &rhs){
if(lhs > rhs) return false;
return true;
}
bool operator>=(const String &lhs, const String &rhs){
if(lhs < rhs) return false;
return true;
}
ostream &operator<<(std::ostream &os, const String &s){
if(s._pstr) os << s._pstr;
//REM 需要尽量避免万一是空指针
return os;
}
//TODO
istream &operator>>(std::istream &is, String &s){
string word;
is >> word;
if(s._pstr){
delete [] s._pstr;
s._pstr = new char[word.size() + 1]();
strcpy(s._pstr, word.c_str());
}
return is;
}
String operator+(const String &lhs, const String &rhs){
String temp = lhs;
temp += rhs;
return temp;
}
String operator+(const String &lhs, const char *s){
String temp(s);
String res = lhs + temp;
return res;
}
String operator+(const char *s, const String &rhs){
String temp(s);
temp += rhs;
return temp;
}
//stringTest.cc
#include "string.hpp"
#include <iostream>
void test(){
String s1("h");
String s2(s1);
cout << "s1: " << s1 << endl;
cout << "s2: " << s2 << endl;
cout << endl << endl;
s2 += s1;
cout << "+= s2: " << s2 << endl;
s2 += "u";
cout << "+= s2: " << s2 << endl;
cout << endl << endl;
s2[0] = 'u';
cout << "[] s2[0]: " << s2 << endl;
cout << "s2.size(): " << s2.size() << endl;
cout << "s2.c_str(): " << s2.c_str() << endl;
cout << endl << endl;
//REM 闇€瑕佸姞鎷彿
cout << "s1 == s2: " << (s1==s2) << endl;
cout << "s1 != s2: " << (s1!=s2) << endl;
cout << "s1 > s2 " << (s1 > s2) << endl;
cout << "s1 < s2 " << (s1 < s2) << endl;
cout << "s1 >= s2 " << (s1 >= s2) << endl;
cout << "s1 <= s2 " << (s1 <= s2) << endl;
String s3;
cin >> s3;
cout <<"s3: " << s3;
String s4;
cin >> s4;
cout << "s3 + s4: " << s3 + s4 << endl;
cout << "s3 + h" << s3 + "h" << endl;
cout << "h + s3" << "h" + s3 << endl;
}
void test1(){
String s1 = "h";
s1 += s1;
cout << s1 << endl;
}
int main()
{
test1();
return 0;
}