目录1、类的定义2、类的访问限定符及封装3、类的实例化4、类和对象的大小5、this 指针6、类的六个默认成员函数构造函数析构函数拷贝构造函数赋值重载函数取地址运算符的重载函数7、运算符重载8、const 成员函数9、 static 成员10、友元11、内部类12、匿名类C语言结构体中只能定义变量,在C中,结构体内不仅可以定义变量,也可以定义函数。比如:之前在数据结构中,用C语言方式实现的栈,结构体中只能定义变量;现在以C方式实现,会发现struct中也可以定义函数。struct Stack { // 成员函数 void Init(int defaultCapacity 4) { a (int*)malloc(sizeof(int) * capacity); if (nullptr a) { perror(malloc申请空间失败); return; } capacity defaultCapacity; top 0; } void Push(int x) { // 扩容 a[top] x; } void Destroy() { free(a); a nullptr; top capacity; } //... // 成员变量 int* a; int top; int capacity; }; int main() { Stack s; s.Init(10); s. Push(1); s.Push(2); s.Push(3); cout s. Top () endl; s.Destroy(); return 0; }1、类的定义class className { // 类体:由成员函数和成员变量组成 }; // 一定要注意后面的分号class为定义类的关键字,ClassName为类的名字,{ }中为类的主体,注意类定义结束时后面分号不能省略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量;类中的函数称为类的方法或者成员函数。类的两种定义方式:1. 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。2. 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名 ::成员变量命名规则建议// 我们看看这个函数,是不是很僵硬? class Date { public: void Init(int year) { // 这里的year到底是成员变量,还是函数形参? year year; } private: int year; }; // 所以一般都建议这样 class Date { public: void Init(int year) { _year year; } private: int _year; }; // 或者这样 class Date { public: void Init(int year) { mYear year; } private: int mYear; }; // 其他方式也可以的,主要看公司要求。一般都是加个前缀或者后缀标识区分就行。2、类的访问限定符及封装访问限定符C实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。【访问限定符说明】1. public修饰的成员在类外可以直接被访问2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止4. 如果后面没有访问限定符,作用域就到 } 即类结束。5. class的默认访问权限为private,struct为public(因为struct要兼容C)注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别C中 struct 和 class 的区别是什么?C需要兼容C语言,所以C中struct可以当成结构体使用。另外C中struct还可以用来定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class 定义的类默认访问权限是private。在类和对象阶段,主要是研究类的封装特性,那什么是封装呢?封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。封装本质上是一种管理,让用户更方便使用类。比如:对于电脑这样一个复杂的设备,提供给用户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日常事务。但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件。3、类的实例化用 类 类型创建对象的过程,称为类的实例化1. 类是对对象进行描述的,定义出一个类并没有分配实际的内存空间来存储它;比如:入学时填写的学生信息表,表格就可以看成是一个类,来描述具体学生信息。2.一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量4、类和对象的大小类和对象的存储方式在定义一个对象后对象的存储的是它的数据成员成员函数没有在对象内部。同一个类和它实例化后的对象sizeof类和 sizeof对象的结果是相同的。类有内存对齐的现象空类和无数据成员的类的大小// 类中仅有成员函数 class A2 { public: void f2() {} }; // 类中什么都没有 --- 空类 class A3 {};sizeofA2和 sizeofA3的结果都是 1 为什么没有成员变量的类对象需要 1 字节的原因是占位表示对象存在如果一个对象的大小是 0 字节那么对象取地址怎么办呢5、this 指针同一个类定义的不同对象各自调用它们的成员函数时为什么每个对象的成员函数的接收到的形参都是各自对象的实参明明都是调用的同一个函数。某类有这些成员函数void Print() { cout _ year - _ month - _ day endl; } void func() { Print(); }经过编译器处理后它们变成了这样void Print(Date* this) { cout this-_ year - this-_ month - this-_ day endl; } void func(Date* this) { this-Print(); }this 是指向对象的指针哪个对象调用了 Print 函数this 指针就指向谁。1、this 指针不能在实参和形参中显式使用但可以在函数内部显式使用。2、this 指针是 类名* const 类型的在函数中不允许修改它的指向。3、this是形参,所以this指针是跟普通参数一样存储在函数调用的栈帧里面。// 1. 下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行 class A { public: void Print() { cout Print() endl; } private: int _a; }; int main() { A* p nullptr; p-Print(); return 0; }答案是 C。p调用Print,不会发生解引用,因为Print的地址不在对象中。p会作为实参传递给this指针。在Print函数中没有对 this 进行解引用操作。如果将 Print 函数该为下面的样子答案就是 Bvoid Print() { cout _a endl; }现在 Print 函数存在对 this 的解引用而 this 接收的实参是 pp 是 nullptr。有人说 p-Print(); 存在对 p 的解引用操作但是从汇编代码来看p-Print(); 只是将 p 作为实参传递给 this。6、类的六个默认成员函数如果一个类中什么成员都没有,简称为空类。空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成6个默认成员函数默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。用户显式实现默认成员函数时不能在类外实现否则会与编译器默认生成的成员函数冲突可以在类中声明在类外实现。构造函数和析构函数是为了解决频繁的初始化和销毁工作减少因为忘记初始化和销毁带来的问题。构造函数构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。其特征如下:1. 函数名与类名相同。2. 无返回值。不需要写 void)3. 对象实例化时编译器自动调用对应的构造函数。4. 构造函数可以重载。全缺省的构造函数与无参构造函数构成重载但调用时存在歧义。5. 如果类中没有显式定义构造函数,则C编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。6、编译器默认生成的构造函数对基本int、double等等类型不做处理对自定义类型class、struct会去调用它的默认构造函数。有些编译器对一个类默认生成的构造函数如果该类有自定义数据成员和基本类型成员时也会将基本类型初始化为 0但不是所有的编译器都会这样做7. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、编译器默认生成的构造函数,都可以认为是默认构造函数。默认构造函数就是不传参就可以调用的函数。示例class A { public: // 有参构造函数 A(int a) { _a a; } // 无参构造函数 A() { _a 1; } void Print() { cout Print() endl; } private: int _a; }; int main() { A a(10);// _a 的初始值是 10 A a; // _a 的初始值是 1; return 0; }注意调用自己写的无参构造函数不能写成这样A a(); // error原因编译器会误以为 A a()是函数声明函数名是 a没有参数返回值类型是 A 类。而应该这样A a;什么时候要写构造函数1、一般情况下,一个类有基本类型成员,就需要自己写构造函数。2、如果类中全部都是自定义类型成员,可以考虑使用编译器自己生成的默认构造函数。3、类中的基本类型成员都有缺省值这时可以不写构造函数。类中的内置类型成员可以给缺省值C11中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。如class A { public: void Print() { cout Print() endl; } private: int _a 1; };在创建 A 类对象时_a 的默认值是 1。但是如果我们自己写了构造函数并在定义对象时初始化该缺省值就没有用了。初始化列表除了在构造函数的函数体内对成员变量赋值的方式来初始化对象还有一种初始化对象的方式即初始化列表。初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个“成员变量”后面跟一个放在括号中的初始值或表达式。class Date { public: Date(int year, int month, int day) :_ year(year) ,_month(month) ,_day(day) {} private: int _year; int _month; int _day; };注意1. 每个成员变量在初始化列表中只能出现一次(也可以不出现)2.类中包含以下成员,必须放在初始化列表位置进行初始化:而不能在构造函数的函数体内初始化引用成员变量const成员变量没有默认构造函数的自定义类型成员引用成员变量和const成员变量的共同特征是必须在定义的时候初始化const int a 10int b a而一个普通变量如 int 类型的变量在定义时可以不初始化int a。在定义对象时引用成员变量和const成员变量随之定义如果在构造函数的函数体内通过赋值的方式来初始化这些成员的话就不满足在定义的时候初始化初始化列表就是为了解决这样的问题。3. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。4. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序。5.在初始化列表初始的变量或自定义类型成员在执行构造函数的函数体之前就以及存在。class A { public: A(int a) :_a1(a) ,_a2(_a1) {} void Print() { cout _ al _ a2 endl; } private: int _a2; int _a1; } int main() { A a(1); a.Print(); return 0; }程序输出1 随机值原因先定义 _a2再定义 _a1所以初始化列表的执行顺序是先执行 _a2(_a1) 这时 _a1 还是随机值赋值给了_a2再执行_a1(a)。初始化列表不能完全代替构造函数它是构造函数的一部分class Stack { public: Stack(int capacity 10) //初始化列表 :_ a((int*)malloc(capacity * sizeof(int))) ,_top(0) ,_capacity(capacity) { //初始化列表不能完成的任务 if (nullptr _ a) { perror(malloc申请空间失败”); exit(-1); } // 要求数组初始化一下 memset(_a, 0, sizeof(int) * capacity); } private: int* _ a; int _top; int _capacity; }单参数的构造函数支持隐式类型转换C11 后支持多参数的构造函数的隐式类型转换class A { int _a public: A(int a): _a(a); {} }; struct B { int _a; int _b; public: B(int a,int b) :_a(a),_b(b) {} } int main() { // 单参数 A a 1; // 构造 拷贝构造优化为构造 // 多参数 B b { 2 , 3 }; return 0; }动态开辟二维数组class AA { public: AA(int row 10, int col 5) :_ row(row) ,_col(col) { _aa (int**)malloc(sizeof(int*) * row); // _aa 数组是指针数组每个数组元素都指向一个 // 一维数组 for (int i 0; i row; i) { aa[i] (int*)malloc(sizeof(int) * col); } private: int ** _ aa; int _row; int _col; };析构函数概念通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。特性1. 析构函数名是在类名前加上字符 ~。2. 无参数无返回值类型不加 void。3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载4. 对象生命周期结束时,C编译系统系统自动调用析构函数。5、编译器默认生成的析构函数对基本int、double等等类型不做处理对自定义类型class、struct会去调用它的析构函数。对象的析构顺序局部对象先析构 → 逆序析构。静态对象 → 程序结束时析构在全局对象之前。全局对象最后析构。堆对象 → 需手动delete。什么时候写析构函数1、一般情况下,有动态申请的堆上的内存空间,就需要写析构函数释放内存空间2、没有动态申请的的堆上的内存空间,不需要写析构函数3、需要释放空间的成员都是自定义类型,不需要写析构函数构造函数和析构函数的优点构造函数和析构函数是为了解决频繁的初始化和销毁工作减少因为忘记初始化和销毁带来的问题。拷贝构造函数拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。类名( const 类名 对象名{ }特征1. 拷贝构造函数是构造函数的一个重载形式。2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。调用拷贝构造函数要传递参数传递参数要拷贝实参给形参又要调用拷贝构造函数又要传递参数传递参数要拷贝实参给形参又要调用拷贝构造函数...3. 若未显式定义,编译器会生成默认的拷贝构造函数。对基本类型完成值拷贝浅拷贝对自定义类型调用它的拷贝构造函数。4.无返回值不需要写 void示例在定义 Date 类时有如下拷贝构造函数Date(const Date d) { _year d._year; _month d._month; _day d._day; }使用拷贝构造函数Date d1(d2);定义日期类对象 d1并用 d2已存在初始化。C 规定基本类型直接拷贝自定义类型必须调用拷贝构造函数来拷贝。如果函数的形参是自定义类型的引用不再调用拷贝构造函数且实参与形参之间没有发生拷贝。若类中有指针变量成员该类的对象使用默认拷贝函数拷贝时拷贝后两个对象的指针成员都指向同一块内存空间这不是我们期望的拷贝结果我们期望的是两个指针成员指向两块内存空间这两块内存空间的存储内容一样这块内存空间就会被析构两次程序会崩溃。与赋值重载函数的区别// 已经存在的两个对象之间复制拷贝 -- 运算符重载函数 Date d1(2024,5,1); Date d2(2024,6,1); d1 d2; // 用一个已经存在的对象初始化另一个对象 -- 构造函数 Date d1(2024,5,1); Date d3(d1); //或 Date d3 d1注意不是只要有 就要调用运算符重载函数如Date d4 d1d1 是已经定义并初始化的对象这时是调用的拷贝构造函数。编译器对拷贝构造的优化class A { public: A(int a) { _a a; } private: int _a; }; void func(A aa) {} int main() { A aa 1;//构造拷贝构造 -- 优化为构造 func(A(1)); //构造拷贝构造 -- 优化为构造 func(1); //构造拷贝构造 -- 优化为构造 return 0; }如果不进行优化则使用 1 去构造一个 A 类临时对象 再用这个临时对象去拷贝构造 aa编译器经过优化以后直接用 1 构造 aa。注意要在同一行的连续拷贝构造或构造才会优化赋值重载函数对 进行运算符重载实现两个同类的对象之间的赋值。class Date { private: int _year; int _month; int _day; public: void operator(const Date d) { _year d._year; _month d._month; _day d._day; } }如有 d1 d2则调用 d1 的 operator 函数。以上代码存在一个问题就是对象之间不能连续赋值比如d1 d2 d3这是因为 operator 函数的返回值是 voidd2 d3这个表达式的值是 void。对 operator 函数做如下改进Date operator(const Date d) { if( this ! d) { _year d._year; _month d._month; _day d._day; } return *this; }1、*this 出了 operator 函数后还存在所以可以使用引用返回减少对象的拷贝函数返回值要调用拷贝构造函数拷贝到寄存器再由寄存器将值赋给表达式2、为了可以检测自己给自己赋值可以加一个 if 判断。用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。默认赋值运算符重载对内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。取地址运算符的重载函数此函数如果我们不写编译器会默认生成以下函数Date* operator() { return this; } const Date* operator()const { return this; }这两个函数构成了函数重载第一个 operaor 函数的形参是 Date* this第二个函数的形参是 const Date* this。有时候我们不想让别人获取到非常对象的地址而常对象的地址可以获取就可以这样做Date* operator() { return nullptr; } const Date* operator()const { return this; }7、运算符重载有以下一个类class Date { public: int _year; int _month; int _day; }如果要比较两个日期类对象的先后一般是写一个函数通过函数调用返回布尔值来判断bool less(const Date x1, const Date x2) { if (x1 ._year x2 ._year) { return true; } else if (x1 ._ year x2 ._ year x1 ._ month x2 ._ month) { return true; } else if (x1 ._ year x2 ._ year x1 ._ month x2 ._ month x1 ._ day x2 ._ day) { return true; } return false; }能否使用 来直接判断呢比如d1 d2只要将函数名该为operator 即可。bool operator(const Date x1, const Date x2) { if (x1 ._year x2 ._year) { return true; } else if (x1 ._ year x2 ._ year x1 ._ month x2 ._ month) { return true; } else if (x1 ._ year x2 ._ year x1 ._ month x2 ._ month x1 ._ day x2 ._ day) { return true; } return false; }C为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。函数名字为:关键字operator后面接需要重载的运算符符号。函数原型:返回值类型 operator操作符(参数列表)注意:● 不能通过连接其他符号来创建新的操作符:比如 operator● 重载操作符必须有一个类类型参数重载操作符有几个操作数就有几个参数。● 用于内置类型的运算符,其含义不能改变,例如:内置的整型,不能改变其含义● 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的 this以上代码仍有问题_year、_month、_day 一般是私有的数据成员在类外不能直接访问私有成员一个解决方法是将 operator 函数作为 Date 类的成员函数调用上面的函数时可以这样调用d1 d2也可以这样d1.opreator(d2)。● .*点和星号::域作用限定符sizeof (求大小) ?:三目注意这 5个运算符不能重载。实现运算符重载的技巧1、先实现 operator上面已实现 和 operator bool operator(const Date d) { return _year d._year _month d._month; _day d._day; }2、有了 operator 和 operator那么 operator 和 operator 只需bool Date::operator (const Date x) { return *this x | | *this X; }bool Date::operator (const Date x) { return !(*this x); }3、有了 operator operator 只需bool Date::operator(const Date x) { return !(*this x); }实现 Date 类 加上天数的 重载Date Date::operator(int day) { day day; while(_day GetMonthDay(_year, _month)) { day - GetMonthDay(_year, _month); _month; if(_month 13) { _year; month 1; } } return *this; }这样Date 类的对象就能计算加上某个天数后的年月日比如int main() { Date d1(2025,5,1); d1 100; d1.print(); return 0; }代码有些失误实际上上面代码实现的是 operator而不是 operator比如 int i 0i 10i 的值并没有改变。问题出在上面代码对 Date 类型直接 了 。Date Date::operator(int day) { day day; while(_day GetMonthDay(_year, _month)) { day - GetMonthDay(_year, _month); _month; if(_month 13) { _year; month 1; } } return *this; } 也有返回值的意义是实现连续的 比如 i j 10。重复使用 operator 实现 Date Date::operator(int day) { Date tmp(*this); tmp._day day; return tmp; }1、使用拷贝构造函数来初始化 tmp不能对 *this 直接 2、tmp 出了函数就被销毁所以不能用引用返回。3、可以用 operator 反过来实现 operator但不如上面的实现方法因为上面的实现方法只创建了两个对象而这种方法创建了四个对象。推荐先实现 再实现 减法类似4、以上代码不能应对 d1 -100 这种情况在实现 - 和 - 后在 operator 中加 if 判断operator- 也是。前置和后置我们已经知道前置和后置的规则Date d1(2025,5,1) d1; d1; //d1 都要 1d1 返回 1 后的 d1d1 返回 1 前的 d1那具体怎么实现呢operator 默认是前置 // 前置 Date Date::operator() { *this 1; return *this; } 已重载见上文那后置 如何实现呢// 后置 //增加这个int参数不是为了接收具体的值,仅仅是占位,跟前置构成函数重载 Date Date::operator(int) { Date tmp *this; *this 1; return tmp; }对类对象的输入与输出的操作符重载流插入操作符 是 C 新增的操作符在库里面已经实现了它对内置类型的重载对于自定义类型比如 Date 类型直接使用 cout d1 endl是不行的d1 是 Date 类的对象需要对 进行运算符重载。void Date::operator(ostream out) { out_ year年_ month月_ day日endl; }但是如果我们用 cout d1 来使用重载后的操作符会发现程序编译不通过其实要这样写才能使用重载后的操作符d1 cout这违背了我们的使用习惯。所以 的重载函数不能写成 Date 类的成员函数而应该写成全局函数。void operator(ostream out,const Date d) { outd._ year年d._ month月d._ day日endl; }新的问题由出现了operator 是全局函数无法直接访问 Date 类对象的数据成员。有两个解决办法1、在 Date 类里再增加这些成员函数int Getyear() { return _year; }operator 函数写成void operator(ostream out,const Date d) { outd.Getyear()年d.Getmonth()月d.Getday()日endl; }2、将 operator 作为 Date 类的友元函数。以上代码无法实现连续打印cout d1 d2 d3解决办法是将 operator 的返回值设为 ostream 类ostream operator(ostream out,const Date d) { outd._ year年d._ month月d._ day日endl; return out; }类对象的输入istream operator(istream in, Date d) { in d. year d. month d ._ day; return in; }8、const 成员函数const对象只能调用const成员函数d2 不能调用 Print 函数是因为 d2 是 const 对象const 对象在调用非 const 函数时传递给 this 指针是 const Date* 类型的地址权限被放大。怎么让 d2 也可以调用 Print 函数呢当然是将 this 指针转换成 const Date* 类型的指针变量由于不能在实参和形参中显式使用 this 指针所以用 const 修饰 this 指针的方法是void Print() constconst 不是修饰 Print 函数的是修饰 this 指针的。哪些成员函数可以加 const如果一个成员函数没有修改对象数据成员的打算单纯打印数据、对 、-、*、/ 、、 等运算符重载的函数等等就可以加 const。如果成员函数可能修改对象的数据成员就不能加 const否则函数的功能不能实现。如果要加 const则在声明和定义都要加。请思考下面的几个问题:1. const 对象可以调用非 const 成员函数吗?2. 非 const 对象可以调用 const 成员函数吗?3. const 成员函数内可以调用其它的非 const 成员函数吗?4. 非 const 成员函数内可以调用其它的 const 成员函数吗?在类中加上 const 和不加 const 的同名函数构成重载。9、 static 成员声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化静态成员变量1、它属于类的每个对象即被类的每个对象所共享。2、它必须在类外初始化而不能在初始化列表或构造函数的函数体内3、在类内不能给缺省值因为缺省值是给初始化列表用的4、类外初始化时可以不加 static类中只是声明。5、它通常是私有的要配合使用静态成员函数访问。6、它存储在静态区,sizeof()不包含静态成员变量的大小。示例class A { static int a; } int A::a 0;静态成员函数1、通常与静态成员变量同时出现用于获取静态成员变量2、它没有this指针不能访问对象的成员3、它既可以通过类名::静态成员函数名访问也可以通过对象名 . 静态成员函数名访问4、类中其他非静态的成员函数可以调用静态成员函数但静态成员函数不能调用类的非静态函数因为静态成员函数没有this指针例子一实现一个类,计算程序中创建出了多少个类对象。class A { public: A() { _scount; } A(const A t) { _scount; } ~A() { --_scount; } static int GetACount () { return _scount; } private: static int _scount; };例子二实现一个类这个类只能在栈或堆上创建对象。class A { public: static A GetStackObj () { A aa; return aa; } static A* GetHeapObj () { return new A; } private: A() {} int _a1 1; int _a2 2; };上面的 A 类中构造函数被 private 访问限定符限定如果我们直接创建一个 A 类的对象是不行的必须调用 GetStackObj () 函数或 GetHeapObj () 函数来创建 A 类的对象但是如果要调用GetStackObj () 函数或 GetHeapObj () 函数又必须有一个 A 类的对象使用静态函数就可以解决这个问题所以将GetStackObj () 函数和GetHeapObj () 函数声明为静态函数。10、友元友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。友元函数友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。特点不是类的成员函数而是一个独立的全局函数。需要在类内声明用friend修饰但定义在类外。没有this指针因为它不是成员函数。可以直接访问类的私有和保护成员。调用时不需要对象除非参数中显式传递对象。友元函数不能用const修饰友元函数可以在类定义的任何地方声明,不受类访问限定符限制一个函数可以是多个类的友元函数示例class MyClass { private: int secret 42; public: friend void showSecret(MyClass obj); // 友元声明 }; // 友元函数的定义不是成员函数 void showSecret(MyClass obj) { cout obj.secret; // 可以直接访问私有成员 } int main() { MyClass obj; showSecret(obj); // 直接调用不需要 obj.showSecret() return 0; }友元函数的应用问题:现在尝试去重载operator ,然后发现没办法将operator重载成成员函数。因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了这样的话 操作符就必须像这样使用了对象名cout。但是实际使用中cout需要作为第一个形参,才能正常使用。所以要将operator重载成全局函数。但又会导致类外没办法访问成员,此时就需要友元来解决。operator同理。class Date { //友元声明 friend ostream operator(ostream out,const Date d); public: Date(int year, int month, int day) :_ year(year) ,_month(month) ,_day(day) {} private: int _year; int _month; int _day; }; ostream operator(ostream out,const Date d) { outd._ year年d._ month月d._ day日endl; return out; }友元类友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。友元关系是单向的,不具有交换性。在 A 类中声明 B 类为其友元类,那么可以在 B 类的成员函数中直接访问 A 类的私有成员变量,但想在 A 类中访问 B 类中私有的成员变量则不行。友元关系不能传递如果 C 是 B 的友元, B 是 A 的友元,则不能说明 C 是 A 的友元。示例class A { private: int secret 100; public: friend class B; // B 是 A 的友元类 }; class B { public: void showAsecret(A a) { cout a.secret; // B 可以访问 A 的私有成员 } }; int main() { A a; B b; b.showAsecret(a); // 输出 100 return 0; }11、内部类概念:如果一个类定义在另一个类的内部,这个类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。注意:内部类是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象来访问外部类中的所有成员。但是外部类不是内部类的友元。特性1. 内部类受外部类的public、protected、private访问限定符限定。2. 内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。3. sizeof(外部类)外部类,不包含内部类。4、在外部类外定义内部类的对象要使用外部类名::内部类名 对象名示例class A { private: static int k; int h; public: class B // B天生就是A的友元 { public: void foo(const A a) { cout k endl; //OK cout a.h endl; //OK } }; int A :: k 1;12、匿名类