之前在c语言中定义一个结构体通常是这样的
// 1、C言中struct用来定义结构体的
typedef struct ListNode_C
{
int _val;
ListNode_C* _next;
ListNode_C* _prev;
}ListNode_C;
虽然在C++中兼容C struct义结构体的用法。但是同时struct也可以用来定义类,所以C+中使用class 和struct一类的区别是什么呢?
最大的区别就是默认的访问限定符
通常C语言中声明的结构体是共有的,是都可以去访问的而在C++使用class去声明一个类会存在一个公有的访问限定符public和一个私有的访问限定符private而通常在public 中提供共有方法对private中 声明的变量去进行一些操作!
class Stack
{
public:
//1.成员函数
void Push(int x);
void Pop();
void Empty();
private:
//2.成员变量
int* _a;
int _size;
int _capacity;
};
如果当你声明了一个class类,但类中没有存在声明的变量该类的大小为1,如果存在多个变量则默认按照最大对齐数的方式去计算类的大小
class Stack
{
public:
private:
};
int main()
{
Stack s1;//类实例化出对象
cout << sizeof(s1); // 1
}
在类中定义的函数是对象可以去调用的,而函数的声明和定义的区别 声明是一种承诺,承诺要干嘛,但是还没做,定义就是把这个事落地了,定义分为两种定义
class Stack
{
public:
//1.成员函数
int reduce(int a,int b);
int Add(int a,int b)//方法一:在类里面定义
{
return a + b;
}
private:
//2.成员变量
int _size;
int _capacity;
};
int Stack::reduce(){//方法一:在类外面定义
return a -b;
}
在class中存在this指向的问题
//【面试题】
//1.this指针存在哪里 (也就是存在进程地址空间的哪个区域?)? 栈上的 因为他是一个形参
//2.this针可以为空吗 ?
class Date
{
public:
//隐含的this指针谁调用就指向谁 默认在编译的时候第一个参数会在编译的时候带上
//void Init(Date* this , int year = 2000 ,int month = 0 ,int day = 1)
void Init(int year = 2000 ,int month = 0 ,int day = 1)
{
_year = year;
_month = month;
_day = day;
//this->_year = year;
//this->_month = month;
//this->_day = day;
}
void Print();
private:
int _year;
int _month;
int _day;
};
void Date::Print()
{
cout<< _year << "年 " << _month << "月 " << _day << "日" << endl;
}
int main()
{
Date d1;
d1.Init();
d1.Print();
d1.Init(2023,7,10); // d1.Init(&d1,2023,7,10);
d1.Print(); // d1.Print(&d1);
return 0;
}
下面这个代码能够说明this针可以为空吗 ?
class A
{
public:
void PrintA()
{
cout <</* this-> */ _a << endl;
}
void Show()
{
cout << "Show()" << endl;
}
private:
int _a;
};
int main()
{
A* p = NULL;
p->PrintA(); //这一行会引发什么?编译不通过? 程序崩溃? 正常运行? // 崩溃 p为空this就找不到a了
p->Show(); //这一行会引发什么?编译不通过? 程序崩溃? 正常运行? // 正常运行
//成员函数存在公共的代码段,所以p>(Show)这里不会去p向的对象上找 访问成员函数才回去找
//常见寄存器的名称:eax ebx ecx..寄存器的特点就是快
}
接下来是class类中最重要的部分,类的默认成员函数
1.类的6个默认成员函数
显式定义就是自己自定义的和class 类名一样的函数用于对变量的初始化
构造函数 -> 在对象构造时调用的函数,这个函数完成初始化工作
构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,
但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
其特征如下 :
1.函数名与类名相同。
2.无返回值。
3.对象实例化时编译器自动调用对应的构造函数。
4.构造函数可以重载。
5.如果类中没有显式定义构造函数,则C+编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。默认构造函数
无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。
class Time
{
public:
Time() {//当默认参数没有的时候编译器会自动调用无参构造生成随机值
hour = 0;
second = 0;
moinute = 0;
cout <<"time()" << endl;
}
private:
int hour;
int second;
int moinute;
};
class Date
{
public:
Date(int year = 2000, int month = 0, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date()
{
_year = 0;
_month = 1;
_day = 1;//当我们没有显式定义构造函数,这里编译器生成无参默认构造函数
}
private:
int _year;
int _month;
int _day;
Time t;//当
};
}
int main()
{
Date d1;//调用默认构造参数 -> 1. 自己实现无参的构造函数 2.自己实现的全缺省构造参数,3. 不写编译器自动生成
d1.Print();
return 0;
}
2.析构函数 ---》 主要完成初始化工作
析构函数是特殊的成员函数。
其特征如下 :
1.析构函数名是在类名前加上字符 ~ 。
2.无参数无返回值。
3.一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
4.对象生命周期结束时,C + 编译系统系统自动调用析构函数。
通常使用场景用于开辟内存空间或者文件操作后的释放作用,用于清理
class Stack
{
public:
Stack(int n = 10)
{
_a = (int*)malloc(sizeof(int) * n);
_size = 0;
_capacity = n;
}
~Stack()//程序结束后会自动调用
{
free(_a);
_a = nullptr;
_size = _capacity = 0;
cout << _a << endl;
}
private:
int* _a;
int _size;
int _capacity;
};
int main()
{
//析构函数:对象生命周期到了以后,自动调用 完成对象里面的清理工作 不是完成d1和d2销毁
//清理的顺序是先入后出
Stack s1;
Stack s2;
//所以顺序是 s2->s1
return 0;
}
3.拷贝构造函数
1.定义:
拷贝构造函数是构造函数的一种重载形式,它可以用来创建一个与已存在的对象一模一样的新对象。对于拷贝构造,它只有单个形参,且该形参必须是对本类类型对象的引用,因为要引用,所以要加const修饰。
2.特征:
1.拷贝构造函数的参数若使用传值方式编译器直接报错, 因为会引发无穷递归调用。2.若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
3.编译器生成的默认拷贝构造函数已经以可完成字节序的值拷贝了。
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// Date(const Date d) // 错误写法
Date(const Date& d) // 正确写法
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(d1);//或者这种Date d2 = d1;
return 0;
}
为什么使用传值会引发无穷递归调用?
画图的方式模拟编译器采用值传递运行情况:
如上图所示,执行date d2(d1); d1传参给拷贝构造的形参d,即需要调用类A的拷贝构造函数时,需要以值方式传进一个A的对象作为实参,那么现在的对象只有d1了,所以会出现 date d(d1),而拷贝的过程中又会调用自身的拷贝构造函数,传值方式会继续传进一个A的对象作为实参,会无休止的递归下去。
默认拷贝构造函数
当我们没有在类中写拷贝构造函数时,编译器会自动生成一个默认的拷贝构造。
系统生成的拷贝构造也会针对成员变量的内置类型和自定义类型做一个区分。对于内置类型的成员变量,编译器会按照被拷贝对象的内存存储字节序完成拷贝,就好比被拷贝的对象有3个int类型成员变量,占12字节内存,编译器会根据该对象的内存和成员初始值拷贝给新对象。
例:
class Time
{
public:
Time()
{
_hour = 1;
_minute = 1;
_second = 1;
}
Time(const Time& t)
{
_hour = t._hour;
_minute = t._minute;
_second = t._second;
cout << ""Time(const Time&)拷贝构造"" << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本类型(内置类型)
int _year = 1970;
int _month = 1;
int _day = 1;
// 自定义类型
Time _t;
};
int main()
{
Date d1;
Date d2(d1); //调用该类的拷贝构造
return 0;
}
解析:创建d2时,调用该类的默认拷贝构造函数,编译器对于内置类型的成员:_year,_month,_day全都拷贝出相同的d1数据值,对于自定义类型成员_t,则会跳转到Time类中调用Time类的拷贝构造。
浅深拷贝之浅拷贝:
对于上例日期类代码,完全可以不用写拷贝构造函数,使用默认的即可,但在特殊情况下,又会有有不同的结果:
typedef int DataType;
class Stack{
public:
Stack(size_t capacity = 10){
_array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array){
perror("malloc申请空间失败");
return;
}
_size = 0;
_capacity = capacity;
}
void Push(const DataType& data){
// CheckCapacity();
_array[_size] = data;
_size++;
}
~Stack(){
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
size_t _size;
size_t _capacity;
};
int main(){
Stack s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
Stack s2(s1);
return 0;
}
我们会发现s1和s2的成员变量_array都指向同一块空间,也就说编译器调用系统生成的默认构造时,把s1._array指向的地址也拷贝给了s2._array,这看起来没什么,等到程序结束完成时,调用析构函数会出现大问题!
程序即将结束时,调用析构函数,会先析构s2,第一次析构完成后,s2._array指向的空间会被释放,其他置为0,也就是等价于s1._array指向的空间也被释放(同一块空间),此时再执行s1的析构函数时,原本释放的空间会被再一次free,就会报错!!! 所以通过这个例子告诫我们,在成员变量是指针,或有文件的情况下,我们不能再用默认的拷贝构造函数了,需要自己生成一个拷贝构造。
之前的日期类全是普通的内置类型成员变量,所以不需要特意去写,编译器能够自主解决完成拷贝,这种拷贝称为浅拷贝。
浅深拷贝之深拷贝:
//深拷贝
Stack(const Stack& st) {
_array = (DataType*)malloc(sizeof(DataType) * st._capacity);
if (_array == nullptr) {
perror("malloc fail");
return;
}
//只需要自己创建一块与st1相同大小的堆空间,其他的还是拷贝st1的数据
memcpy(_array, st._array, _capacity = st._capacity);
_size = st._size;
_capacity = st._capacity;
}
而深拷贝就是单靠编译器生成的默认构造不能满足需求,需要自主去写一个,称为深拷贝。
规律:
需要写析构函数的类,都需要写拷贝构造函数(Stack类)
不需要写析构函数的类,默认生成的拷贝构造即可。(Date类)
拷贝构造函数典型调用场景:
使用已存在对象创建新对象
函数参数类型为类类型对象
函数返回值类型为类类型对象
class Date
{
public:
Date(int year, int minute, int day)
{
cout << "Date(int,int,int):" << this << endl;
}
Date(const Date& d)
{
cout << "Date(const Date& d):" << this << endl;
}
~Date()
{
cout << "~Date():" << this << endl;
}
private:
int _year;
int _month;
int _day;
};
Date Test(Date d) //返回值类型是Date,需要调用拷贝构造,函数参数是Date型,也需要调用拷贝构
//造
{
Date temp(d);
return temp;
}
int main()
{
Date d1(2022,1,13);
Test(d1);
return 0;
}
代码解析:执行Test(d1);时,编译器进入Test函数中,因为形参是Date类型,所以调用一次拷贝构造函数,输出拷贝语句;之后执行Date temp(d);——创建对象Temp,又要调用一次拷贝构造函数;之后执行return temp返回类对象时,又要调用一次拷贝构造函数,总共调用三次拷贝构造,充分验证了上面调用拷贝构造的三种场景。
4.赋值操作符重载
C + 为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型
函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为 : 关键字operator后面接需要重载的运算符符号。
函数原型 : 返回值类型operator操作符(参数列表)
注意 :
。不能通过连接其他符号来创建新的操作符 : 比如operator@
。重载操作符必须有一个类类型或者枚举类型的操作数
。用于内置类型的操作符,其含义不能改变,例如 : 内置的整型 + ,不能改变其含义
。作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的操作符有一个默认的形参this,限定为第一个形参
。.*、::、sizeof、?:、. 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
//运算符有几个操作数,operator重载的函数就有几个参数
class Date
{
public:
Date(int year = 2000, int month = 0, int day = 1)
{
cout << "Date(int year = 2000, int month = 0, int day = 1)" << endl;
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//自定义类型是不能用运算符的,要用就得实现重载函数,自定义类型用的时候等价于调用这个重载函数
bool operator == (const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
bool operator > (const Date& d)
{
if (_year > d._year)
return true;
else if (_year == d._year && _month == d._month)
return true;
else if (_year == d._year && _month == d._month && _day == d._day)
return true;
return false;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2020, 5, 12);
Date d2(2020, 5, 13);
d1 == d2;//编译如何调用->这里编译会转换成operator=(dld):所这里就是一个函数调用
// operator == (d1, d2);// 但是我们一般不会这样写,因为这样可读性不好
d1 > d2;
return 0;
}
实现一个完善的日期类
class Date
{
public:
int _Getmonths(int year,int month)
{
static int MonthDays[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
//是二月且是闰年返回 29
if (month == 2 && (year % 4 && year % 100 != 0) || year % 400 == 0)
return 29;
return MonthDays[month];
}
Date(int year = 0,int month = 1,int day = 1)
{
if (year >= 0 && month >= 1 && day >= 1 && day <= _Getmonths(year, month))
{
_year = year;
_month = month;
_day = day;
}
else
{
cout << "非法日期" << endl;
}
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
bool operator == (const Date& d)
{
return _year == d._year && _month == d._month && _day == d._day;;
}
inline bool operator <(const Date& d)
{
if (_year < d._year)
return true;
else if (_year == d._year && _month < d._month)
return true;
else if (_year == d._year && _month == d._month && _day < d._day)
return true;
return false;
}
bool operator != (const Date& d)
{
return !(*this == d);// 复用上面的来实现
}
bool operator <=(const Date& d)
{
return *this < d || *this == d; // 复用上面的来实现
}
bool operator >(const Date& d)
{
return !(*this <= d);// 复用上面的来实现
}
bool operator >=(const Date& d)
{
return !(*this < d);// 复用上面的来实现
}
//复用+=事件
Date operator +(int day)
{
Date ret(*this); // 用d1 拷贝构造一个ret
ret += day;
return ret;
}
Date& operator +=(int day)//返回自己需要带引用
{
if (day < 0)
{
return *this -= -day;
}
_day += day;
while (_day > _Getmonths(_year, _month))
{
// 如果日期的天不合法,就需要往月进
_day -= _Getmonths(_year, _month);
++_month;
if (_month == 13)
{
++_year;
_month = 1;
}
}
return *this;
}
//复用-=事件
Date operator -(int day)
{
Date ret(*this);
ret._day -= day;
return ret;
}
Date& operator -=(int day)
{
if (day < 0)
{
return *this += -day;
}
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}
_day += _Getmonths(_year, _month);
}
return *this;
}
//++d1 =》 d1.operator++(&d1)
Date& operator ++()
{
/*if (_day == _Getmonths(_year, _month))
{
_day = 1;
++_month;
}
else if (_day == _Getmonths(_year, _month) && _month == 12)
{
++_year;
_month = 1;
_day = 1;
}
else
_day += 1;*/
* this += 1;
return *this;
}
//d1++ =》 d1.operator++(&d1,0)
Date operator ++(int)
{
Date ret(*this);
*this += 1;
return ret;//返回加之前的值
}
// --d1 d1.operator--(&d1)
Date& operator --()
{
*this -= 1;
return *this;
}
//--d1 d1.operator--(&d1,0)
Date operator --(int)
{
Date ret(*this);
*this -= 1;
return ret;//返回加之前的值
}
//计算相差天数
int operator-(const Date& d)
{
int flag = 1;
Date max = *this;
Date min = d;
if (*this == d)
return 0;
if (*this < d)
{
max = d;
min = *this;
flag = -1;
}
int totalDay = 0;
while (min != max)
{
++min;
++totalDay;
}
return totalDay * flag;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
/*Date d1;
d1.Print();*/
Date d1(2019, 4, 11);
Date d2(2020, 4, 11);
cout << (d1 - d1) << endl;
cout << (d2 - d1) << endl;
//Date d3(0, 0, 0);
//d3.Print();
/*cout << (d1 < d2) << endl;
cout << (d1 <= d2) << endl;
cout << (d1 > d2) << endl;
cout << (d1 >= d2) << endl;
cout << (d1 == d2) << endl;
cout << (d1 != d2) << endl;*/
// 是否要重载一个运算符,看的是这个运算符是否对这个类的对象有意义
/*Date d4 = d2 + 100;
d4.Print();*/
}
//1、我们不实现时,编译器生成的默认构造函数和析构函数。针对成员变量 : 内置类型就不处理,
自定义类型会调这个成员对象的构造和析构。
//2、我们不实现时,编译器生成拷贝构造和operator = ,会完成按字节的值拷贝(浅拷贝
也就是说有些类,我们是不需要去实现拷贝构造和operator = 的,因为编译器默认生成就可以用。
//比如 : Date是这样
class Date
{
public:
//必须在初始化列表初始化:
//1、const成员变量
//2、引用成员变量
//3、没有默认构造函数的自定义成员变量
//成员变量在初始化列表初始化的顺序是声明的顺序,不是初始化列表出现定义的顺序
//构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用 explicit 关键字的作用就是防止类构造函数的隐式自动转换
explicit Date(int year = 0,int month = 1,int day=1)
:year(year),month(month),day(day)//使用初始化列表进行成员变量初始化比在构造函数体内使用赋值语句效率更高,并且可以用于初始化const成员变量或引用类型成员变量。
{
}
private:
int year;
int month;
int day;
};
面试题:实现一个类,计算中程序中创建出了多少个类对象。
classA
{
public:
A() {++_scount;}
A(const A& t) {++_scount;}
static int GetAcount(){return _scount;}
private:
static int _scount;
};
int A::_count = 0;
void TestA()
{
cout<<A::GetAcount()<<endl;
A a1,a2;
A a3(a1);
cout<A::GetACount)<<endl
}
/*static员
概念
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量:用static修饰的
成员函数,称之为静态成员函数。静态的成员变量一定要在类外进行初始化
面试题
静态成员函数可以调用非静态成员函数吗? 不行 因为静态的成员函数中没有this指针
非静态成员函数可以调用非静态成员函数吗?可以 突破类域+访问限定符就可以访问,类里面是一个整体都在这个类域中,类里面不受访问限定符的限制
突破类域访问的两种方式。
//1、static成员变量不存在对象中,存在静态区,属于这个类的所有对象,也是属于这个类。
//2、static员函数,没有this针,不使用对象就可以调用。->类名::func);
//3、static员函数中,不能访问非静态的成员(成员变量+成员函数)
/*
5.const成员函数点击链接跳转详细
//使用const后的指向问题
// const Date * p ->*p 指向的对象
// Date const * p2 ->*p2 指向的对象
// Date* const p ->p 指针本身
//const在*之前指向的对象 在*之后指向本身
6.取地址及const地址操作符重载
将const
修饰的“成员函数”称之为const成员函数, const
修饰类成员函数,实际修饰该成员函数隐含的this
指针,表明在该成员函数中不能对类的任何成员进行修改。我们来看看下面的代码:
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
{
Year = year;
Month = month;
Day = day;
}
void Print(Date* const this)
{
cout << Year << "-" << Month << '-' << Day << endl;
}
private:
int Year;
int Month;
int Day;
};
int main()
{
Date d1(2023, 5, 20);
d1.Print();
const Date d2(2023, 5, 21);
d2.Print();
return 0;
}
- 以上代码对于
d2
是编译不通过的,这是为什么呢?
- 当对象
d1
调用成员函数this
指针来访问该对象的成员变量和成员函数。this
指针的类型:类名* const this
,const修饰this,即成员函数中,不能修改this
指针;- 而对于对象
d2
,当调用成员函数时,编译器同样会自动将该对象作为第一个参数传递给成员函数,但不同的是,由于const
修饰d2
,因此this
指针的类型应该是const Date* const this
,因此不能给*this
赋值,所以导致了报错。
因此,为了解决以上问题。C++是允许在函数后加上一个const,这样就能正常运行了:
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
{
Year = year;
Month = month;
Day = day;
}
void Print() const
{
cout << this->Year << "-" << this->Month << '-' << this->Day << endl;
}
private:
int Year;
int Month;
int Day;
};
int main()
{
Date d1(2023, 5, 20);
d1.Print();
const Date d2(2023, 5, 21);
d2.Print();
return 0;
}
【程序结果】
【总结】
- 成员函数在后加上
const
后,普通和const
修饰的对象都能用。- 但注意:由于
const
修饰的对象,其形参中的*this
被const
修饰,也就说明不能对*this
进行修改。因此,如果成员函数内要修改对象成员变量的函数千万不要用const
修饰对象。- 如果声明和定义分离,声明的函数和定义的函数都要加
const
取地址及const取地址操作符重载 首先先来看看一下代码:
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
Year = year;
Month = month;
Day = day;
}
private:
int Year;
int Month;
int Day;
};
int main()
{
Date d1(2023, 5, 20);
const Date d2(2023, 5, 21);
cout << &d1 << endl;
cout << &d2 << endl;
return 0;
}
【程序结果】
通过以上结果我们发现:const
取地址操作符重载不用重新定义,编译器默认会生成。当然,如果想自己定义也是可以的,代码如下:
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
Year = year;
Month = month;
Day = day;
}
Date* operator&()
{
return this;
}
const Date* operator&() const
{
return this;
}
private:
int Year;
int Month;
int Day;
};
int main()
{
Date d1(2023, 5, 20);
const Date d2(2023, 5, 21);
cout << &d1 << endl;
cout << &d2 << endl;
return 0;
}
【程序结果】
需要注意的是:重载const
取地址操作符时,返回的指针类型必须是const
指针,否则会导致编译错误。
特殊使用情况
虽然这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如 不想让别人获取到指定的内容
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
Year = year;
Month = month;
Day = day;
}
Date* operator&()
{
return nullptr;
}
const Date* operator&() const
{
return nullptr;
}
private:
int Year;
int Month;
int Day;
};
int main()
{
Date d1(2023, 5, 20);
const Date d2(2023, 5, 21);
cout << &d1 << endl;
cout << &d2 << endl;
return 0;
}
【程序结果】
还有一个叫做友元函数看这里就可以了C++:友元(看这一篇就够了)_c++ 友元函数_孙 悟 空的博客-CSDN博客
还有一个叫做内联函数 就是类里面再声明一个类用的比较少就不赘述了!