第十章 友元、内部类、局部类、运算符重载、仿函数
友元
- 友元包括友元函数和友元类
- 如果将函数A(非成员函数)声明为类C的友元函数,那么函数A就能直接访问类C对象的所有成员(即使是private成员)
- 如果将类A声明为类C的友元类,那么类A的所有成员函数都能直接访问类C对象的所有成员(即使是private成员)
-
友元破坏了面向对象的封装性,但在某些频繁访问成员变量的地方可以提高性能
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; }
内部类
- 如果将类A定义在类C的内部,那么类A就是一个内部类(嵌套类)
- 内部类的特点
- 支持public、protected、private权限
- 成员函数可以直接访问其外部类对象的所有成员(反过来则不行)
- 成员函数可以直接不带类名、对象名访问其外部类的static成员
- 不会影响外部类的内存布局
- 可以在外部类内部声明,在外部类外面进行定义
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; }
- 内部类 – 声明和实现分离
- 内部类的使用场合
- 某个类仅仅在一个类中使用
局部类
- 在一个函数内部定义的类,称为局部类
- 局部类的特点
- 作用域仅限于所在的函数内部
- 其所有的成员必须定义在类内部,不能类内部声明外部定义了,不允许定义static成员变量
- 成员函数不能直接访问函数的局部变量(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)
- 运算符重载(操作符重载):可以为已存在的运算符增加一些新的功能
-
全局函数、成员函数都支持运算符重载
#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运算符
;
-
操作符重载实战
//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 <<")"; }
- 模仿C++标准库函数string(好好研究研究!!!!)
-
封装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); }
-
抽出一个函数
//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; }
-
使用:
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; }
- 疑问:什么情况下才会调用重载的 “=”操作符函数呢?看下面常见的几种”=“操作
- 核心: 凡是出现对象创建,即:类 变量名 = xxx 那就一定调用的是构造,不会是=操作符,=操作符作用在已经创建的对象上,=左边一定是已经初始化过的对象
//情况1:不会调用,只会调用隐式构造函数:String(const char *cstring); String str = "123"; //情况2: 不会调用,因为这是通过一个对象,初始化一个对象,只会调用拷贝构造函数 String str2 = str; //情况3:会调用操作符重载“=” //拷贝构造 String str3 = "456"; //下面这俩是=操作符 str3 = str2; str3 = "567";
- 解疑惑:
- 隐式构造、拷贝构造什么时间调用?
- 既然是构造函数,那么一定是初始化一个对象
- 因此表现形式一定是:
类 类名 = xxx;
- 调用隐式构造与拷贝构造的区别
- 隐式构造:xxx 不是与“=”左边类相同的对象
- 拷贝构造: xxx 一定是与“=”左边类相同的对象
- String类重载的“=”操作符是什么时候会调用呢?
- “=”操作符就是个浅拷贝,而且是原来已经存在的,而且系统默认的是浅拷贝。
- 我们可以通过重写拷贝构造函数实现深拷贝,但是拷贝构造函数是初始化一个对象的时候调用的。也就是初始化的时候才会调用,但是如果是已经存在的对象间赋值呢?
对象1 = 对象2;
这个是不会调用拷贝构造函数的 - 因此,这才有了”=“的重载,从而也说明只有在
对象=对象
的时候才会调用重载的运算符函数 - 从另一个方面也可以说明,重载运算符函数的调用场合
- 前面说过,运算符重载也是一个特殊的函数:
返回值 operator运算符 (参数,...)
- 那么重载运算符函数也就是一个成员函数,那么非静态的成员函数调用这一定是对象
- 因此重载运算符函数调用方式一定是:
对象.operator运算符 (参数,...)
等价于:对象 运算符 对象
;
- 前面说过,运算符重载也是一个特殊的函数:
- 隐式构造、拷贝构造什么时间调用?
- 疑问:什么情况下才会调用重载的 “=”操作符函数呢?看下面常见的几种”=“操作
-
优化
//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;
}
运算符重载注意点
- 有些运算符不可以被重载,比如
- 对象成员访问运算符: .
- 域运算符: ::
- 三目运算符: ?:
- sizeof
- 有些运算符只能重载为成员函数,比如
- 赋值运算符: =
- 下标运算符: [ ]
- 函数运算符: ( )
- 指针访问成员:->
单例模式补充
- 单例需要禁止掉一些行为
- 拷贝行为
- 赋值行为
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; }
仿函数(函数对象)
- 仿函数:将一个对象当作一个函数一样来使用
-
对比普通函数,它作为对象可以保存状态
#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; }