第十章 友元、内部类、局部类、运算符重载、仿函数

友元

  1. 友元包括友元函数和友元类
  2. 如果将函数A(非成员函数)声明为类C的友元函数,那么函数A就能直接访问类C对象的所有成员(即使是private成员)
  3. 如果将A声明为类C的友元类,那么A的所有成员函数都能直接访问类C对象的所有成员(即使是private成员)
  4. 友元破坏了面向对象的封装性,但在某些频繁访问成员变量的地方可以提高性能

     class Point {
     	//友元函数:让add函数成为当前类的朋友,然后add函数就可以直接访问当前类的成员了
     	//这句话可以放在这个类的任何地方
     	friend Point add(const Point &, const Point &);
        
     	//友元类,Math成为当前类的友元类,那么在Math内部就可以直接访问当前类的所有成员
     	friend  class Math;
     	int m_x;
     	int m_y;
     public:
     	int getX() const { return this->m_x; }
     	int getY() const { return this->m_y; }
     	Point(int x, int y) :m_x(x), m_y(y) {
        
     	}
     };
        
     Point add(const Point &point1, const Point &point2) {
        
     	/*return Point(point1.getX()+point2.getX(),point1.getY()+point2.getY());*/
     	//直接访问Point类的所有成员
     	return Point(point1.m_x + point2.m_x, point1.m_y + point2.m_y);
     }
        
     class Math {
     	Point dele(const Point &point1, const Point &point2) {
     		/*return Point(point1.getX()-point2.getX(),point1.getY()-point2.getY());*/
     		//直接访问Point类的所有成员
     		return Point(point1.m_x - point2.m_x, point1.m_y - point2.m_y);
     	}
     };
        
     int main() {
     	Point point(10, 20);
     	Point point2(20, 30);
     	Point point3 = add(point, point2);
        
     	cout << endl;
     	getchar();
     	return 0;
     }
    

内部类

  1. 如果将类A定义在类C的内部,那么类A就是一个内部类(嵌套类)
  2. 内部类的特点
    1. 支持public、protected、private权限
    2. 成员函数可以直接访问其外部类对象的所有成员(反过来则不行)
    3. 成员函数可以直接不带类名、对象名访问其外部类的static成员
    4. 不会影响外部类的内存布局
    5. 可以在外部类内部声明,在外部类外面进行定义
     class Person {
     	int m_age;
     	static int ms_legs;
     	static void test(){}
        
     public:
     	Person() {
     		cout << "Person()" << endl;
     	}
     //1.支持public、protected、private权限
     //Car内只能在Person内部使用
     //private:  
     	//car
     	class Car {
     		int m_price;
     	public:
     		Car() {
     			cout << "Car()" << endl;
     		}
     		void run() {
     			//2. 成员函数可以直接访问其外部类对象的所有成员(反过来则不行)
     			Person perosn;
     			perosn.m_age = 10;
        
     			//3. 成员函数可以直接不带类名、对象名访问其外部类的static成员
     			ms_legs = 10;	
     			test();
     		}
     	};
     };
     int Person::ms_legs = 2;
        
     //5. 可以在外部类内部声明,在外部类外面进行定义
     class Point {
     	class Math {
     		void test();
     	};
     };
        
     void Point::Math::test() {
        
     }
        
     int main() {
     	//使用
     	Person person;
     	Person::Car car;
        
     	getchar();
     	return 0;
     }
    
  3. 内部类 – 声明和实现分离 图1
  4. 内部类的使用场合
    1. 某个类仅仅在一个类中使用

局部类

  1. 在一个函数内部定义的类,称为局部类
  2. 局部类的特点
    1. 作用域仅限于所在的函数内部
    2. 其所有的成员必须定义在类内部,不能类内部声明外部定义了,不允许定义static成员变量
    3. 成员函数不能直接访问函数的局部变量(static变量除外)
     int g_age = 10;
     void test() {
     	int age = 10;
     	static int s_age = 20;
     	//局部类
     	class Person {
     	public:
     		static void test() {
     		//成员函数不能直接访问函数的局部变量(static变量除外)
     		//age;
     		//全局变量可以
     		g_age = 20;
     		//静态变量可以
     		s_age;
        
     		}
     	};
        
     	Person person;
     	person.test();
     }
        
     int main() {
     	//注意:并不是每调一次都会定义一次类,类是在编译时候就完成了,每次调用只是调用函数
     	test();
        
     	getchar();
     	return 0;
     }
    

运算符重载(operator overload)

  1. 运算符重载(操作符重载):可以为已存在的运算符增加一些新的功能
  2. 全局函数、成员函数都支持运算符重载

     #include<iostream>
     using namespace std;
        
     class Point {
     	friend Point operator+(Point & , Point &);
     	int m_x;
     	int m_y;
     public:
     	Point(int x,int y):m_x(x),m_y(y) {}
        	
     	//成员函数 运算符重载
     	Point operator-(Point &P) {
     		return Point(this->m_x - P.m_x, this->m_y - P.m_y);
     	}    	
     	void display() {
     		cout << "x = " << this->m_x << "-y = " << this->m_y << endl;
     	}
     };
        
     //全局函数
     //运算符(操作符)重载:operator运算符
     Point operator+(Point &P1, Point &p2) {
        
     	return Point(P1.m_x + p2.m_x, P1.m_y + p2.m_y);
     }
     int main() {
     	Point p1(20, 30);
     	Point p2(30, 40);
        
     	//如果想这么做肯定是不行的,但是如果把“+”重载就可以了
     	Point p3 = p1 + p2;
     	//等价
     	//Point p3 = operator+(p1, p2);
     	p3.display();
        
     	//成员函数
     	Point p4 = p1 - p2;
     	//等价
     	//Point p4 = p1.operator-(p2);
     	p4.display();
        
     	getchar();
     	return 0;
     }
    
    • 从上面示例可以得出结论
    • 运算符重载格式如下:

        返回值类型 operator运算符 (参数,...){}
      
    • 那么可以看成运算符重载就是一个函数,而这个函数的函数名是固定的:operator运算符;
  3. 操作符重载实战

     //Point.h
     #pragma once
     #include <iostream>
     using namespace std;
        
     class Point {
         //ostream是cout这个对象的类型
         friend ostream &operator<<(ostream &, const Point &);
         int m_x;
         int m_y;
     public:
         Point(int x, int y);
         // 运算符(操作符)重载
         Point operator+(const Point &point) const;
         Point operator-(const Point &point) const;
         //取反 (3,5) -> (-3,-5)
         const Point operator-() const;
         Point &operator+=(const Point &point);
         Point &operator-=(const Point &point);
         bool operator==(const Point &point);
         bool operator!=(const Point &point);
         // 前++
         Point &operator++();
         // 后++
         const Point operator++(int);
     };
        
     //Point.m
     #include "Point.h"
    
     Point::Point(int x, int y) :m_x(x), m_y(y) { }
        
     // 运算符(操作符)重载
     Point Point::operator+(const Point &point) const {
     	return Point(this->m_x + point.m_x, this->m_y + point.m_y);
     }
        
     Point Point::operator-(const Point &point) const {
     	return Point(this->m_x - point.m_x, this->m_y - point.m_y);
     }
        
     const Point Point::operator-() const {
     	return Point(-this->m_x, -this->m_y);
     }
        
     Point &Point::operator+=(const Point &point) {
     	this->m_x += point.m_x;
     	this->m_y += point.m_y;
     	return *this;
     }
        
     Point &Point::operator-=(const Point &point) {
     	this->m_x -= point.m_x;
     	this->m_y -= point.m_y;
     	return *this;
     }
        
     bool Point::operator==(const Point &point) {
     	// 1 YES true
     	// 0 NO false
     	/*if (this->m_x == point.m_x && this->m_y == point.m_y) {
     		return 1;
     	}
     	else {
     		return 0;
     	}*/
     	return (this->m_x == point.m_x) && (this->m_y == point.m_y);
     }
        
     bool Point::operator!=(const Point &point) {
     	return (this->m_x != point.m_x) || (this->m_y != point.m_y);
     }
        
     // 前++
     Point &Point::operator++() {
     	this->m_x++;
     	this->m_y++;
     	return *this;
     }
        
     // 后++
     const Point Point::operator++(int) {
     	Point point(this->m_x, this->m_y);
     	this->m_x++;
     	this->m_y++;
     	return point;
     }
        
     //cout
     //ostream是cout这个对象的类型
     ostream &operator<<(ostream &cout, const Point &point) {
     	return cout << "(" << point.m_x << ", " << point.m_y <<")";
     }
    
  4. 模仿C++标准库函数string(好好研究研究!!!!
    1. 封装1:

       //String.h 文件
       #pragma once
       #include<iostream>
       using namespace std;
       class String
       {
       	//搞定字符串的打印
       	friend ostream &operator<<(ostream &, String &);
       private:
       	char *m_cstring;
       public:
       	//搞定字符串的初始化赋值(隐式构造)
       	String(const char *cstring);
       	//拷贝构造
       	String(const String &string);
       	~String();
       	//搞定字符串的修改(=右边接收c字符串)
       	String &operator=(const char *cstring);
       	//搞定String的赋值(= 右边接收c字符串接收String)
       	String &operator=(const String &string);
       };
              
       //String.cpp文件
       #include "String.h"
      
       String::String(const char *cstring){
       	if (!cstring) return;
       	cout << "String(const char *) - new[] - " << cstring << endl;
       	this->m_cstring = new char[strlen(cstring) + 1]{};
       	strcpy(this->m_cstring, cstring);
              
       }
              
       String::String(const String &string) {
       	 *this = string.m_cstring;
       	 //等价
       	 //(*this).operator=(string.m_cstring);
       }
              
       String::~String(){
              	
       	cout << "~String() - delete[] - " << this->m_cstring << endl;
       	/*
       	if (!this->m_cstring) return;
       	delete[] this->m_cstring;
       	this->m_cstring = NULL;*/
       	//上面3句等价于这一句,这句会调用下面的String &String ::operator=(const char *)函数
       	//本质是一样的
       	operator=(NULL);
              
       	//下面都等价
       	// *this = NULL;
       	/*
       	(*this).operator=(NULL);
       	this->operator=(NULL);
       	operator=(NULL);
       	*/
       }
              
              
       ostream &operator<<(ostream &cout, String &string) {
       	if (!string.m_cstring) return cout;
       	return cout << string.m_cstring;
       }
              
       //搞定字符串的修改
       String &String ::operator=(const char *cstring) {
       	//防止“自己= 自己”
       	if (this->m_cstring == cstring)  return *this;
              
       	//释放旧的字符串
       	if (this->m_cstring){
       		cout << "delete[] - " << this->m_cstring << endl;
       		delete[] this->m_cstring;
       		this->m_cstring = NULL;
       	}
       	//指向新的字符串
       	if (cstring){
       		cout << "new[] - " << cstring << endl;
       		this->m_cstring = new char[strlen(cstring) + 1]{};
       		strcpy(this->m_cstring, cstring);
       	}
       	cout << "String &String ::operator=(const char *)" << endl;
       	return *this;
        }
              
       String &String:: operator=(const String &string) {
       	return operator=(string.m_cstring);
       }
              
      
    2. 抽出一个函数

       //String.h文件
       #pragma once
       #include<iostream>
       using namespace std;
       class String
       {
       	//搞定字符串的打印
       	friend ostream &operator<<(ostream &, String &);
       private:
       	char *m_cstring;
       	//内部代码抽取
       	String &assign(const char *cstring);
       public:
       	//搞定字符串的初始化赋值(隐式构造)
       	String(const char *cstring);
       	//拷贝构造
       	String(const String &string);
       	~String();
       	//搞定字符串的修改(=右边接收c字符串)
       	String &operator=(const char *cstring);
       	//搞定String的赋值(= 右边接收c字符串接收String)
       	String &operator=(const String &string);
       };
              
       //String.cpp文件
       #include "String.h"
      
       String::String(const char *cstring){
      
       	assign(cstring);
       }
              
       String::String(const String &string) {
              
       	 assign(string.m_cstring);
       }
              
       String::~String()
       {
       	assign(NULL);
       }
              
              
       //搞定字符串的修改
       String &String ::operator=(const char *cstring) {        
       	return assign(cstring);
        }
              
       String &String:: operator=(const String &string) {
       	//return operator=(string.m_cstring);
       	return assign(string.m_cstring);
       }
              
       String &String::assign(const char *cstring) {
       	// 指向一样的堆空间
       	if (this->m_cstring == cstring) return *this;
              
       	// 释放旧的字符串
       	if (this->m_cstring) {
       		cout << "delete[] - " << this->m_cstring << endl;
              
       		delete[] this->m_cstring;
       		this->m_cstring = NULL;
       	}
              
       	// 指向新的字符串
       	if (cstring) {
       		cout << "new[] - " << cstring << endl;
              
       		this->m_cstring = new char[strlen(cstring) + 1]{};
       		strcpy(this->m_cstring, cstring);
       	}
              
       	return *this;
       }
              
        ostream &operator<<(ostream &cout, String &string) {
       	if (!string.m_cstring) return cout;
       	return cout << string.m_cstring;
       }  
      
    3. 使用:

       int main() {
       	//1. C语言字符串
       	const char *name1 = "111";
       	const char *name2 = "222";
       	//这么写肯定不对的,字符串不支持拼接
       	//const char *name3 = name1 + name2;
              
       	//2. C++标准库带的字符串处理类 string(首字母小写)
       	string str1 = "xxx";
       	string str2 = "yyy";
       	string str3 = str1 + str2 + "zzz";
              
       	//下面我们通过string模仿一个相同功能的String
              
       	//1. 实现等于(通过隐式构造创建一个对象)
       	String strr = "123";
       	//等价于(隐式构造,前面讲过)
       	//String strr("123");
       	char name[] = "111";
       	//也是隐式构造,因为name为C字符串,而不是String类型,等价于: String strr2(name);
       	String strr2 = name;
       	//直接通过构造函数创建初始化一个String
       	String strr3("222");
       	/***上面通过隐式构造实现了等于:String str = C常量/变量字符串 **/
              
       	//2. 实现修改
       	String strr4 = "333";
       	//这句话等价于:String strr4r("333");  这个是隐式构造
       	//修改字符串(已存在对象 = (浅拷贝)匿名对象)
       	strr4 = "444";
       	//这句话有错误,因为“=”是浅拷贝,String("444")这个临时对象赋值给strr4,然后这个临时对象会释放
       	//那么此时strr4指向的是一个已经释放的堆空间内存,很危险,同时原来指向的那个对空间“333”,没有回收掉
       	//解决办法,重载=运算符
              
       	//3. 实现String的赋值
       	String strrr1 = "111";
       	String strrr2 = "222";
       	//这么写肯定错误,因为=右边不是字符串了,而是一个String对象,那第2步重载的=(字符串),不能使用
       	//因此也需要重载 =(String)
       	strrr1 = strrr2;
              
       	//4. 拷贝构造
       	String strrr3 = "111";
       	//这个是拷贝构造!!!!(注意区分!!!)
       	//因为:1. 是初始化一个对象 2. =右边String类型相同,所以为拷贝构造
       	String strrr4 = strr3;
              
       	//实现打印
       	cout << strr2<<strr3<<strr4 << endl;
              
       	getchar();
       	return 0;
       }
      
      1. 疑问:什么情况下才会调用重载的 “=”操作符函数呢?看下面常见的几种”=“操作
        1. 核心: 凡是出现对象创建,即:类 变量名 = xxx 那就一定调用的是构造,不会是=操作符,=操作符作用在已经创建的对象上,=左边一定是已经初始化过的对象
         //情况1:不会调用,只会调用隐式构造函数:String(const char *cstring);
         String str = "123";
         //情况2: 不会调用,因为这是通过一个对象,初始化一个对象,只会调用拷贝构造函数
         String str2 = str;
                    
         //情况3:会调用操作符重载“=”
         //拷贝构造
         String str3 = "456";
         //下面这俩是=操作符
         str3 = str2;
         str3 = "567";
        
      2. 解疑惑:
        1. 隐式构造、拷贝构造什么时间调用?
          1. 既然是构造函数,那么一定是初始化一个对象
          2. 因此表现形式一定是:类 类名 = xxx;
          3. 调用隐式构造与拷贝构造的区别
            1. 隐式构造:xxx 不是与“=”左边类相同的对象
            2. 拷贝构造: xxx 一定是与“=”左边类相同的对象
        2. String类重载的“=”操作符是什么时候会调用呢?
          1. “=”操作符就是个浅拷贝,而且是原来已经存在的,而且系统默认的是浅拷贝。
          2. 我们可以通过重写拷贝构造函数实现深拷贝,但是拷贝构造函数是初始化一个对象的时候调用的。也就是初始化的时候才会调用,但是如果是已经存在的对象间赋值呢?对象1 = 对象2;这个是不会调用拷贝构造函数的
          3. 因此,这才有了”=“的重载,从而也说明只有在对象=对象的时候才会调用重载的运算符函数
          4. 从另一个方面也可以说明,重载运算符函数的调用场合
            1. 前面说过,运算符重载也是一个特殊的函数:返回值 operator运算符 (参数,...)
            2. 那么重载运算符函数也就是一个成员函数,那么非静态的成员函数调用这一定是对象
            3. 因此重载运算符函数调用方式一定是:对象.operator运算符 (参数,...) 等价于:对象 运算符 对象
    4. 优化

       //String.h文件
       #pragma once
       #include<iostream>
       using namespace std;
       class String{
       	//搞定字符串的打印
       	friend ostream &operator<<(ostream &, String &);
       private:
       	char *m_cstring = NULL;
       	//内部代码抽取,输入一个c字符串,返回一个String字符串
       	String &assign(const char *cstring);
              	
       	//返回一个拼接好的C字符串
       	char *join(const char *cstring1, const char *cstring2);
       public:
       	//搞定字符串的初始化赋值(隐式构造)
       	String(const char *cstring="");
       	//拷贝构造
       	String(const String &string);
       	~String();
       	//搞定字符串的修改(=右边接收c字符串)
       	String &operator=(const char *cstring);
       	//搞定String的赋值(= 右边接收String)
       	String &operator=(const String &string);
              
       	//搞定字符串的拼接(+右边接收c字符串)
       	String operator+(const char *cstring);
       	//搞定String的拼接(+ 右边接收String)
       	String operator+(const String &string);
       	//+=
       	String &operator+=(const char *cstring);
       	String &operator+=(const String &string);
       	// []
       	char operator[](int index);
       	//>
       	bool operator>(const char *cstring);
       	bool operator>(const String &string);
       };
      
        //String.cpp文件
        #include "String.h"
      
       String::String(const char *cstring){
       	assign(cstring);
       }
              
       String::String(const String &string) {
       	assign(string.m_cstring);
       }
              
       String::~String(){
       	assign(NULL);
       }
              
       ostream &operator<<(ostream &cout, String &string) {
       	if (!string.m_cstring) return cout;
       	return cout << string.m_cstring;
       }
              
       //搞定字符串的修改
       String &String ::operator=(const char *cstring) {
       	return assign(cstring);
       }
              
       String &String:: operator=(const String &string) {
       	return assign(string.m_cstring);
       }
              
       //搞定字符串的拼接(+右边接收c字符串)
       String String:: operator+(const char *cstring) {
       	String str;
       	char *newstr = join(this->m_cstring, cstring);
       	if (newstr){
       		//释放旧空间
       		str.assign(NULL);
       		//指向新空间
       		str.m_cstring = newstr;
       	}
       	return str;
       }
       //搞定String的拼接(+ 右边接收c字符串接收String)
       String String:: operator+(const String &string) {
       	return operator+(this->m_cstring);
       }
              
       String &String ::operator+=(const char *cstring) {
       	char *newstr = join(this->m_cstring, cstring);
       	if (newstr) {
       		//释放旧空间
       		this->assign(NULL);
       		//指向新空间
       		this->m_cstring = newstr;
       	}
       	return *this;
       }
       String &String ::operator+=(const String &string) {
       	return operator+=(string.m_cstring);
       }
              
       char String::operator[](int index) {
       	if (index < 0 || !this->m_cstring) return '\0';
       	if (index >= strlen(this->m_cstring)) return '\0';
       	//C语言字符串本来就有这种功能
       	return this->m_cstring[index];
       }
              
       bool String:: operator>(const char *cstring) {
       	if (!this->m_cstring || !cstring) return 0;
       	//strcmp是C函数
       	return strcmp(this->m_cstring, cstring) > 0;
       }
       bool String:: operator>(const String &string) {
       	return operator>(string.m_cstring);
       }
              
       //输出2个c字符串,拼接为一个c字符串
       char *String :: join(const char *cstring1, const char *cstring2) {
       	if (!cstring1 || !cstring2) return NULL;
       	char *newstr = new char[strlen(cstring1) + strlen(cstring2) + 1]{};
       	strcat(newstr, cstring1);
       	strcat(newstr, cstring2);
       	return newstr;
       }
       String &String::assign(const char *cstring) {
       	// 指向一样的堆空间
       	if (this->m_cstring == cstring) return *this;
              
       	// 释放旧的字符串
       	if (this->m_cstring) {
       		cout << "delete[] - " << this->m_cstring << endl;
              
       		delete[] this->m_cstring;
       		this->m_cstring = NULL;
       	}
              
       	// 指向新的字符串
       	if (cstring) {
       		cout << "new[] - " << cstring << endl;
              
       		this->m_cstring = new char[strlen(cstring) + 1]{};
       		strcpy(this->m_cstring, cstring);
       	}
              
       	return *this;
       }
      

调用父类的运算符重载函数

#include<iostream>
using namespace std;

class Person {
	int m_age;
public:
	Person &operator=(const Person &person) {
		this->m_age = person.m_age;
	}
};

class Student :public Person {
	int m_score;
public:
	Student &operator=(const Student &student) {
		//调用父类的运算符重载函数
		Person :: operator=(student);
		this->m_score = student.m_score;
	}
};

cin/cout 重载

class Point {
	friend ostream &operator<<(ostream &, const Point &);
public:
	int m_x;
	int m_y;
	Point(int x, int y) :m_x(x), m_y(y) { }
};

// output stream
ostream &operator<<(ostream &cout, const Point &point) {
	return cout << "(" << point.m_x << ", " << point.m_y << ")";
}

// input stream
istream &operator>>(istream &cin, Point &point) {
	return cin >> point.m_x >> point.m_y;
}

int main() {
	Point p1(10, 20);
	//整个对象输入
	cin >> p1;
	cout << p1 << endl;
	return 0;
}

运算符重载注意点

  1. 有些运算符不可以被重载,比如
    1. 对象成员访问运算符: .
    2. 域运算符: ::
    3. 三目运算符: ?:
    4. sizeof
  2. 有些运算符只能重载为成员函数,比如
    1. 赋值运算符: =
    2. 下标运算符: [ ]
    3. 函数运算符: ( )
    4. 指针访问成员:->

单例模式补充

  1. 单例需要禁止掉一些行为
    1. 拷贝行为
    2. 赋值行为
     class Rocket {
     public:
     	//3.
     	static Rocket *shateRocket() {
     		//严格来讲,这段代码需要考虑线程安全问题。因此这里面需要加线程安全
     		if (ms_instance == NULL){
     			ms_instance = new Rocket();
     		}
     		return ms_instance;
     	}
     	static void deleteRocket() {
     		if (ms_instance == NULL) return;
     		//也要注意线程安全问题
     		//delete后,一定要清空,否则出现野指针错误,因为ms_instance仍然指向原来的内存
     		//下次创建的时候,ms_instance不为NULL,所以还是返回这个内存,野指针
     		delete ms_instance;
     		ms_instance = NULL;
     	}
     	~Rocket(){
        
     	}
     private:
     	//1
     	Rocket() {}
     	//2
     	static Rocket *ms_instance;
     	//拷贝构造函数也弄成私有的
     	Rocket(const Rocket &rocket) {};
     	//禁止赋值
     	void operator=(const Rocket &rocket) {};
     };
     //静态成员初始化
     Rocket * Rocket::ms_instance = NULL;
        
     int main() {
     	Rocket *p1 = Rocket::shateRocket();
     	Rocket *p2 = Rocket::shateRocket();
     	//禁止拷贝
     	//Rocket p2(*p1);
     	//禁止赋值
     	//*p1 = *p2;
        	
     	getchar();
     	return 0;
     }
    

仿函数(函数对象)

  1. 仿函数:将一个对象当作一个函数一样来使用
  2. 对比普通函数,它作为对象可以保存状态

     #include<iostream>
     using namespace std;
        
     class Sum {
     public :
     	int operator()(int a, int b) {
     		return a + b;
     	}
     };
        
     int main() {
     	Sum sum;
     	//等价于:sum.operator()(3,4);
     	cout << sum(3, 4) << endl;
     	getchar();
     	return 0;
     }
    
Table of Contents