友元

友元介绍

  • 友元是一种允许非类成员函数访问类的非公有成员的一种机制
  • 可以把一个函数指定为类的友元,也可以把整个类指定为另一个类的友元
    • 友元函数
    • 友元类

友元函数

  • 友元函数在类的作用域外定义,但它需要在类体中进行说明;
  • 为了与该类的成员函数加以区别,定义的方式是在类中用关键字 friend 说明该函数,格式如下: friend 类型 友元函数名(参数表);
  • 友元的作用在于提高程序的运行效率。
  • 友元函数虽然不是类的成员函数,但是可以访问类的私有的,公有的,保护的成员对象。

友元函数注意事项

  • 友元函数不是类的成员函数,在函数体中访问对象的成员,必须用对象名加运算符‘.'加对象成员名。但友元函数可以访问类中的所有成员(公有的,私有的,保护的),一般函数只能访问类中的公有成员。
  • 友元函数不受类中的访问权限关键字限制,可以把它放在类的公有,私有,保护部分,结果一样;
  • 某类的友元函数的作用域并非该类的作用域。如果该友元函数是另一类的成员函数,则其作用域为另一类的作用域,否则与一般函数相同。
  • 友元函数破坏了面向对象程序设计类的封装性,所以友元函数如果不是必须使用,则尽量少用,或用其他手段保证封装性。

友元类

  • 如果某类 B 的成员函数会频繁的存取另一个类 A 的数据成员,而 A 的数据成员的 Private/Protected 限制造成 B 存取的麻烦,B 只能通过 A 的 public 的成员函数进行间接存取。
  • 把 B 做成 A 类的友元类,即 A 类向 B 类开发其 Private/Protected 内容,让 B 直接存取。
  • 友元类:一个类可以作为另一类的友元
  • 友元类的声明: friend class 类名;

友元类注意事项

  • 友元关系是单向的:A 是 B 的友元类(在 B 类中 friend class A),并不代表 B 也是 A 的友元类;
  • 友元关系不能被传递:A 是 B 的友元类,B 又是 C 的友元类,并不代表 A 是 C 的友元类;
  • 友元关系不能被继承:A 是 B 的友元类,C 继承自 A,并不代表 C 是 B 的友元类。

运算符重载(一)

运算符重载

  • 运算符重载允许把标志运算符(如 + - * / < > 等)应用于自定义数据类型的对象
  • 直观自然,可以提高程序的可读性
  • 体现了 C++ 的可扩充性
  • 运算符重载仅仅只是语法上的方便,它是另一种函数调用的方式
  • 运算符重载,本质上是函数重载
  • 不要滥用重载,因为它只是语法上的方便,所以只有在涉及的代码更容易写,尤其是更容易读时才有必要重载。

成员函数重载

  • 成员函数原型的格式:函数类型 operator 运算符(参数表);
  • 成员函数定义的格式:类名::operator 运算发(参数表) {函数体}
Complex operator+(const Complex &other); // 声明
// 实现
Complex Complex::operator+(const Complex &other)
{
int r = real_ + other.real_;
int i = imag_ + other.imag_;

return Complex(r,i);
}
// 调用
Complex c3 = c1 + c3; // 等价于 Complex c3 = c1.operator+(c2) 或 Complex c3 = operator+(c1,c2);

非成员函数重载

  • 友元函数原型的格式 friend 函数类型 operator 运算符(参数表);
  • 友元函数定义的格式 friend 函数类型 类名::operator 运算符(参数表){函数体;}
  • 成员函数调用的优先级大于非成员函数

运算符重载规则

  • 运算符重载不允许发明新的运算符
  • 不能改变运算符操作对象的个数
  • 运算符被重载后,其优先级和结合性不会改变
  • 不能重载的运算符:::、 ?:、 .、 .*、 sizeof
  • 一般情况下,单目运算符最好重载为类的成员函数,双目运算符最后重载为类的友元函数
  • 以下一些双目运算符不能重载为类的友元函数:= () [] ->
  • 类型转换运算符只能以成员函数方式重载
  • 流运算符只能以友元的方式重载

运算符重载(二)

++ 运算符重载

前置 ++ 运算符重载

  • 成员函数的方式重载,原型为:函数类型 & operator++()
  • 友元函数的方式重载,原型为:friend 函数类型 & operator++(类类型 &);

后置自增和后置自减的重载

  • 成员函数的方式重载,原型为:函数类型 &operator++(int)
  • 友元函数的方式重载,原型为:friend 函数类型 &operator++(类类型 &,int);
// 前置 ++
Integer& operator ++();

Integer& Integer::operator ++()
{
++n_;
return *this;
}

// 后置 ++
Integer operator ++(int i);

Integer Integer::operator ++(int i)
{
//用临时对象
Integer tmp(n_);
n_++;
return tmp;
}

!运算符重载

当字符串非空的时候返回真,字符串空的时候返回假

bool operator !() const;

bool String::operator !() const
{
return strlen(str_) != 0;
}

赋值运算符重载

当赋值操作时,默认是浅拷贝的,需要重载为深拷贝。

String& operator= (const String& other);

String& String::operator= (const String& other)
{
if (this == &other)
return *this;

delete[] str_;
str_ = AllocAndCopy(other.str_);
return *this;

}

运算符重载(三)

String类实现

[] 运算符重载


char& operator[](unsigned int index);
const char& operator[](unsigned int index) const;

char& MyString::operator [](unsigned index)
{
// 返回是引用,返回值可以作为左值
return str_[index];

// non const 版本调用 const 版本
return const_cast<char&>(static_cast<const MyString&>(*this)[index]);
}

const char& MyString::operator [](unsigned index) const
{
// 返回是引用,返回值可以作为左值
return str_[index];
}

  • static_cast < type-id > ( expression ),该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。
  • dynamic_cast < type-id > ( expression )
    说明:该运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void *;如果type-id是类指针类型,那么expression也必须是一个指针,如果type-id是一个引用,那么expression也必须是一个引用。
  • const_cast<type_id> (expression)
    说明:该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。
    常量指针被转化成非常量指针,并且仍然指向原来的对象;常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。

+ 运算符重载

// 以友元的方式重载

friend MyString operator+(const MyString& s1, const MyString& s2);

MyString operator+(const MyString& s1, const MyString& s2)
{
int len = strlen(s1.str_) + strlen(s2.str_) + 1;
char* newstr = new char[len];
memset(newstr,0,len);
strcpy(newstr,s1.str_);
strcat(newstr,s2.str_);

MyString tmp(newstr);
delete newstr;
return tmp;

/* 借助 += 来实现
MyString str = s1;
str += s2;
return srt;
*/

}

+= 运算符重载

MyString& operator+=(const MyString& other);

MyString& MyString::operator +=(const MyString& other)
{
// 借助 + 运算符来实现
int len = strlen(str_) + strlen(other.str_) + 1;
char* newstr = new char[len];
memset(newstr,0,len);
strcpy(newstr,str_);
strcat(newstr,other.str_);

delete[] str_;
str_ = newstr;
return *this;
}

流运算符c重载

  • C++ 的 I/O 流库的一个重要特性就是能够支持新的数据类型的输出和输入
  • 用户可以通过对插入符(<<) 和提取符 (>>)进行重载来支持新的数据类型
  • 流运算符的重载只能使用友元函数进行重载
  • 为什么一定要用友元函数进行重载?第一个参数是流对象,不是自身,返回的是流对象的引用。
  • friend istream& operator>>(istream&, 类类型&);
  • friend ostream& operator<<(ostream&, const 类类型&);
// << 运算符重载
friend ostream& operator <<(ostream& os, const MyString& str);

ostream& operator <<(ostream& os, const MyString& str)
{
os << str.str_;
return os;
}

// >> 运算符重载


运算符重载(四)

类型转换运算符

  • 必须是成员函数,不能是友元函数
  • 没有参数(操作数是什么)
  • 不能指定返回类型(其实已经指定了)
  • 函数原型 operator 类型名();
  • 可以将当前的类类型转换诶其他类型
// 类型转换
operator int();

Integer::operator int()
{
return n_;

}

Integer n(100);
n = 200; // 会调用转换构造函数然后调用=运算符
n.Display();

int sum = add(n, 100); // 参数传递会调用类型转换运算符的重载
cout << sum <<endl;
int x = n; // 隐式转换
int y = static_cast<int>(n); // 显示转换

-> 指针运算符

//例子
// DB类相当于智能指针,包装了DBHelper
#include <iostream>
using namespace std;

class DBHelper
{
public:
DBHelper()
{
cout << "DBHelper..." << endl;
}
~DBHelper()
{
cout << "~DBHelper..." << endl;
}

void Open()
{
cout << "Open..." << endl;
}

void Close()
{
cout << "Close..." << endl;
}

void Query()
{
cout << "Query..." << endl;
}
};


class DB
{
public:
DB()
{
db_ = new DBHelper;
}
~DB()
{
delete db_;
}
DBHelper* operator ->()
{
return db_;
}

private:
DBHelper* db_;
};

int main(void)
{
DB db;
db->Open();
db->Query();
db->Close();
return 0;
}

operator new 和 operator delete

  • void* operator new(size_t size)
  • void operator delete(void* p)
  • void operator delete(void* p, size_t size)
  • void* operator new(size_t size,const char* file, long line)
  • void operator delete(void* p, const char* file, long line)
  • void* operator new[](size_t size)
  • void operator delete[](void* p)
  • void operator delete[](void* p,size_t size)

new 三种用法

  • new operator Test* p1 = new Test(100); // new operator = operator new + 构造函数的调用
  • operator new 可以被重载
  • placement new 直接返回已经存在的地址
// placement new
char chunk[10]; // 在已存在的内存空间上创建对象
Test* p2 = new (chunk) Test(200); // operator new(size_t, void *_Where)
// placement new 不分配内存
#include <iostream>
using namespace std;

class Test
{
public:
Test(int n) : n_(n){cout << "Test(int n) : n_(n)" << endl;}
~Test()
{
cout << "~Test()" << endl;
}

void* operator new(size_t size)
{
cout << "void* operator new(size_t size)"<< endl;
void* p = malloc(size);
return p;
}

void operator delete(void* p)
{
cout << " void operator delete(void* p)"<< endl;
free(p);
}
void operator delete(void* p, size_t size)
{
cout << " void operator delete(void* p, size_t size)"<< endl;
free(p);
}
//重载 placement new
void* operator new(size_t size, void *p)
{
return p;
}

void operator delete(void*, void*)
{

}

// 可以有多个参数
void* operator new(size_t size, char* file, long line)
{
cout << file << ":" << line << endl;
void* p = malloc(size);
return p;
}

void operator delete(void* p, char* file, long line)
{
cout << file << ":" << line << endl;
free(p);
}

int n_;
};

// 全局的重载
void* operator new(size_t size)
{
cout << "global void* operator new(size_t size)" << endl;
void *p = malloc(size);
return p;
}

void operator delete(void *p)
{
cout << "global void operator delete(void *p)"<< endl;
free(p);
}
void* operator new[](size_t size)
{
cout << "global void* operator new[](size_t size)" << endl;
void *p = malloc(size);
return p;
}

void operator delete[](void *p)
{
cout << "global void operator delete[](void *p)"<< endl;
free(p);
}


int main(void)
{
Test* p1 = new Test(100); // new operator = operator new + 构造函数的调用
delete p1;
char* str = new char;
delete str;
char* str1 = new char[100];
delete[] str1;
char chunk[10];

// placement new
Test* p2 = new (chunk) Test(200); //operator new(size_t, void* _Where)
// placement new 不分配内存 + 构造函数的调用
cout << p2->n_ << endl;
p2->~Test(); // 显示调用析构函数
// Test* p3 = (Test*)chunk;
Test* p3 = reinterpret_cast<Test*>(chunk);
cout << p3->n_ << endl;
#define new new(__FILE__,__LINE__)
// Test* p4 = new(__FILE__,__LINE__) Test(300); // 可以找出内存泄露的地方
Test* p4 = new Test(300);
delete p4;

return 0;
}

//输出结果:
/*
void* operator new(size_t size)
Test(int n) : n_(n)
~Test()
void operator delete(void* p)
global void* operator new(size_t size)
global void operator delete(void *p)
global void* operator new[](size_t size)
global void operator delete[](void *p)
Test(int n) : n_(n)
200
~Test()
200
../25cpp/03.cpp:105
Test(int n) : n_(n)
~Test()
void operator delete(void* p)
*/