> 文档中心 > C++类和对象

C++类和对象

目录

C++类和对象

1. 类的引入

C++对于struct的升级

1. 结构体可以做类型 

2.里面可以定义函数

2. 类的定义 

类的两种定义方式

3. 类的访问限定符及封装 

3.1. 访问限定符

3.2. 封装

4. 类的作用域

5. 类的实例化 

6. 类对象模型  

6.1. 计算类对象的大小

6.2. 类的多文件写法

2. this指针  

3. 类的6个默认成员函数

3.1. 构造函数

3.1.1 概念

3.1.2 特性

3.1.3 explicit关键字

3.1.4 总结

3.2. 析构函数

3.2.1 概念

3.2.2 特性

3.3. 拷贝构造

3.3.1 概念

3.3.2 特征

3.3.3 匿名对象 赋值 编译器的优化

精题(重要!)

3.3.4 总结

3.4. 运算符重载

3.4.1 概念

3.4.2 注意

3.4.3 全局的operator==

3.4.4 运算符重载放进类里面 

3.4.5 全局和类域同名运算符重载

3.4.6 赋值运算符重载 

3.4.7 提问

3.5 &运算符重载 and const成员

3.5.1 const修饰类的成员函数

3.5.2 取地址及const取地址操作符重载  

4. 初始化列表  

5. static成员 

5.1 概念

5.2 访问成员函数

5.2.1 共有成员函数

5.2.2 static成员函数

6. C++11 的成员初始化新玩法

概念

示例

7. 友元  

7.1 友元函数

7.2 注意

7.3 友元类

8. 内部类

8.1 概念


C语言总结在这常见八大排序在这

作者和朋友建立的社区:非科班转码社区-CSDN社区云💖💛💙

期待hxd的支持哈🎉 🎉 🎉

最后是打鸡血环节:你只管努力,剩下的交给天意🚀 🚀 🚀  

C++类和对象

首先,说到类和对象,就要先谈谈什么是面向过程,什么是面向对象了,在之前

C 语言是 面向过程 的, 关注 的是 过程 ,分析出求解问题的步骤,通过函数调用逐步解决问题。 C++ 基于面向对象 的, 关注 的是 对象 ,将一件事情拆分成不同的对象,靠对象之间的交互完成。

1. 类的引入

C 语言中,结构体中只能定义变量,在 C++ 中,结构体内不仅可以定义变量,也可以定义函数。

C++对于struct的升级

1. 结构体可以做类型 

2.里面可以定义函数

PS:有人可能会问了, 编译器不是从上到下编译吗,那上面遇到成员变量会不会不认识?其实在类里面你要把类当成一个整体,如果在上面遇到了不认识的,会把整个类作用域运行完再去看是否能找到哈。

 但是上述都是为了兼容C的,其实C++中更喜欢用class来代替。

2. 类的定义 

class 定义类的 关键字, ClassName 为类的名字, {} 中为类的主体,注意 类定义结束时后面 分号 类中的元素称为 类的成员: 类中的 数据 称为 类的属性 或者 成员变量 ; 类中的 函数 称为 类的方法 或者 成员函数

类的两种定义方式

1. 声明和定义全部放在类体中,需要注意:成员函数如果 在类中定义 ,编译器可能会将其当成 内联函数 处理。

2. 声明放在.h文件中,类的定义放在.cpp文件中 

如果不加上类作用域会报错不是因为成员是private,是对于访问限定符限制的是从内外直接访问,而类内是都可以直接访问的

一般情况下,更期望采用第二种方式。  

3. 类的访问限定符及封装 

3.1. 访问限定符

C++实现封装的方式: 用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其 接口提供给外部的用户使用

【访问限定符说明】

1. public 修饰的成员在类外可以直接被访问 2. protected private 修饰的成员在类外不能直接被访问( 此处 protected private 是类似的)(到继承不一样) 3. 访问权限 作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止 4. class 默认访问权限 private struct public( 因为struct要兼容 C)

C++ struct class 的区别是什么? C++ 需要兼容 C 语言,所以 C++ struct 可以当成结构体去使用。另外 C++ struct 还可以用来定义类。和class 是定义类是一样的,区别是 struct 的成员默认访问方式是 public class 是的成员默认访问方式是private。

3.2. 封装

面向对象的三大特性: 封装、继承、多态 那什么是封装呢? 封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行 交互。 封装本质上是一种管理 我们使用类数据和方法都封装到一起。不想给别人看到的,我们使用protected/private 成员 封装 起来。 开放 一些共有的成员函数对成员合理的访问。所以封装本质是一种管理。 其实就是:1.数据和方法封装到一起类里面2.想给你自由访问的设计为public,不想的就是pvivate和protected一般情况设计类:成员数据都是私有或者保护,想访问的函数是共有的,不想给你访问的私有或者保护。

4. 类的作用域

类定义了一个新的作用域 ,类的所有成员都在类的作用域中 在类体外定义成员,需要使用 :: 作用域解析符指明成员属于哪个类域。 类的作用域和之前说的命名空间域是很像的,就如也可以出现不同域的同名函数。

5. 类的实例化 

用类类型创建对象的过程,称为类的实例化 1. 类只是 一个 模型 一样的东西,限定了类有哪些成员,定义出一个类 并没有分配实际的内存空间 来存储它(比如说创建了一个类,类里面有成员变量,但是因为没有开辟空间,所以是声明) 2. 一个类可以实例化出多个对象, 实例化出的对象 占用实际的物理空间,存储类成员变量 3. 做个比方。 类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图 ,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间(一个类可以实例化多个对象,就像一个图纸可以造多个房子一样)

6. 类对象模型  

6.1. 计算类对象的大小

class A {public: void PrintA() {     cout<<_a<<endl; }private: char _a;};

问题:类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?如何计算一个类的大小?那我们先要知道 类对象的存储方式如果 对象中包含类的各个成员 每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一个类创建多个对象时,每个对象中都会保存一份代码,相同代码保存多次,浪费空间。那么如何解决呢? 只保存成员变量,成员函数存放在公共的代码段(网图)

然后我们现在去计算一下看看到底怎么存储的

先补充一下前面的结构体内存对齐规则

1. 第一个成员在与结构体偏移量为 0 的地址处。 2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS 中默认的对齐数为 8  (可用 #progma pack(n)来改变默认对齐数大小)3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。 4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

然后是测试

结论:一个类的大小,实际就是该类中 成员变量 之和,当然也要进行内存对齐,注意空类的大小,空类比 较特殊,编译器给了空类一个字节来唯一标识这个类。(操作系统规定每一个变量或者函数都要有其占位符以证明其存在,就比如sizeof(void)=1一样)

6.2. 类的多文件写法

这里我们小写一个栈(不实现)(图有详解)

2. this指针  

2.1. 初识this

对于上述类,有这样的一个问题:

Date 类中有 SetDate Display 两个成员函数,函数体中没有关于不同对象的区分,那当 s1 调用 SetDate 函数时,该函数是如何知道应该设置s1 对象,而不是设置 s2 对象呢?

C++ 中通过引入 this 指针解决该问题,即: C++ 编译器给每个 非静态的成员函数 增加了一个隐藏的指针参 数,让该指针指向当前对象 ( 函数运行时调用该函数的对象 ) ,在函数体中所有成员变量的操作,都是通过该 指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成 所以上面的程序其实是这样的 (其实Date* this还不准确,真正应该是 Date* const this)但是其实是报错的,我们不能这样去写,但是cout里面可以直接写哈 类里面是声明,只有对象里面才是实实在在的定义
但是我们不能直接把形参里面的this写出来,这是编译器做的事情,但是我们可以直接使用
  (实参传地址和形参接收都是 第一个参数哈(关于this)) (这里访问的并不是声明的年月日,而是 相应对象的成员)2.2.  this 指针的特性 1. this 指针的类型:类类型 * const   因为this指针是const,所以在类内不可以修改,但是this是可以在类外初始化的,就比如下面,此时this就是nullptr。(隐藏传参传的时候就直接传值,不用传地址了) 记住nullptr只要没有解引用,程序就不会崩溃 PS:空指针一定是0地址,但不一定是物理内存的0地址,是虚拟地址空间的,虚拟地址空间要最后通过页表映射到物理地址上

2. 只能在 成员函数 的内部使用 3. this 指针本质上其实是一个成员函数的形参 ,是对象调用成员函数时,将对象地址作为实参传递给 this形参。所以对象中不存储 this 指针 。(而且我们算对象大小的时候也没有算this指针,也说明了不在对象里面,其实是存在与栈里面的,形参这些都是在栈里面) 当然有些编译器会进行优化把this放到寄存器里面(因为快)所以也有可能是在寄存器里面并不是push压参数 4. this 指针是成员函数第一个隐含的指针形参,一般情况由编译器通过 ecx 寄存器自动传递,不需要用户 传递

网图 

3. 类的6个默认成员函数

如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?并不是的,任何一个类在我们不写的情况下,都会自动生成下面6 个默认成员函数。 class Date {};

网图: 

3.1. 构造函数

如果没有构造函数,我们以往来说调用

我们可以Init设置对象信息,但是如果每次创建对象都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?

3.1.1 概念

构造函数 是一个 特殊的成员函数,名字与类名相同 , 创建类类型对象时由编译器自动调用 ,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次

3.1.2 特性

构造函数 是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象 其特征如下: 1. 函数名与类名相同2. 返回值(也不要去写void)3. 对象实例化时编译器 自动调用 对应的构造函数。 4. 构造函数可以重载 自己实现构造函数(有参和无参)

注意:注意这里的构造函数的调用(构造函数可以重载)

如果类中没有显式定义构造函数,则 C++ 编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。默认生成的就相当于上面的无参构造函数,啥也没有,但是如何有自定义成员(类),那就会默认调用自定义成员的构造函数。 无参构造函数和全缺省构造函数

无参的构造函数(1)全缺省的构造函数(2)都称为默认构造函数,并且默认构造函数只能有一个(多个编译器不知道调用哪个)。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数(3),都可以认为是默认成员函数(不用传参就可以调用)。

敲重点:在稍微老一点的编译器(VS2013)默认生成构造函数对内置类型成员变量不做处理(随机值),对于自定义类型成员变量才处理,但是作者的VS2022是处理了的,但是有警告 总结:如果有内置类型的成员,或者需要显示传参初始化,那么都要自己实现构造函数。 对于上面C++11有所补充(如下图)
  如果构造函数只初始化内置类型不初始化自定义类型  发现会调用自定义类型的默认构造函数

//发现会调用自定义类型的默认构造函数

 //如果不写自定义类型的默认构造函数,编译器会初始化为0

3.1.3 explicit关键字

构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用

class Date{public: Date(int year) :_year(year) {}  explicit Date(int year) :_year(year) {} private: int _year; int _month: int _day; };void TestDate(){ Date d1(2022);  // 用一个整形变量给日期类型对象赋值 // 实际编译器背后会用2050构造一个无名对象,最后用无名对象给d1对象进行赋值 d1 = 2050; }

上述代码可读性不是很好, explicit 修饰构造函数,将会禁止单参构造函数的隐式转换 但是有很大可能优化,会直接变成拷贝构造

那编译器这样优化的意义是什么呢? 

是为了用在这种情况:

190 191 192 那种调用就很麻烦,而且s1的作用仅仅只是去调用又很浪费,所以193这种调用(隐式类型转换)的好处就显示出来了。 

PS:const引用临时变量后会延迟他的生命周期

3.1.4 总结

构造函数不写会默认生成,啥也不做。写了就不生成,一般一个C++类,都要自己写构造函数。一般只有少数情况可让编译器自动生成

1.类里面都是自定义类型成员,并且这些成员提供了默认构造函数。

2.如果还要内置类型成员,声明时给了缺省值。

默认构造有三种,编译器自动生成的,无参的,全缺省的。特性就是都是不用传参编译器自己调用的。

对于默认构造函数,对内置类型不处理,对与自定义类型调用他们的默认构造(没有就编译错误)就要用初始化列表(后面讲)

如果自己写了构造函数,但是没有处理自定义类型,也会调用自己的默认构造函数,如果有的没有初始化,编译器也会初始化为0(VS2022),老一点的应该是不会初始化的如VS2013,就报错了,VS2022是警告。

3.2. 析构函数

3.2.1 概念

构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而 对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。 (如malloc,new)

3.2.2 特性

析构函数是特殊的成员函数 1. 析构函数名是在类名前加上字符 ~ 2. 无参数无返回值。 3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数(也是啥也不做)。 4. 对象生命周期结束时, C++ 编译系统系统自动调用析构函数。 也和上面的构造函数一样,默认生成的析构函数,内置类型成员不做处理,自定义类型成员去调用他的析构函数。 什么时候要去自己实现析构函数?当自己直接对资源进行管理的时候,就要去,这个资源是堆上的,因为栈上的是编译器会去处理。如栈,就要自己去free。

3.3. 拷贝构造

3.3.1 概念

构造函数 只有单个形参 ,该形参是对 类类型对象的引用 ( 一般常用 const 修饰 ) ,在用 已存在的类类型对象 创建新对象时由编译器自动调用。(类里面也会自动生成浅拷贝构造函数)

3.3.2 特征

1. 拷贝构造函数 是构造函数的一个重载形式 2. 拷贝构造函数的 参数只有一个 必须使用引用传参 ,使用 传值方式会引发无穷递归调用 我们先自己实现一个

如果不用&,就会因为调用函数就会调用拷贝构造,然后就要传参,但是传参又要拷贝构造,拷贝构造就又要传参,导致死循环,所以加上&。同时拷贝构造有所规定(如果不改变拷贝就加上const,怕函数写错了) 规定内置类型是直接拷贝,自定义类型调用其拷贝构造

浅拷贝构造可能的问题: 析构函数重复free,同一个空间不能释放两次。要用深拷贝(后面讲)解决,这也是为什么自定义类型要用他自己的拷贝构造完成,之后还有其他的细节 当我们没有自己去实现的时候(浅拷贝)

我们不写编译器会自动生成一个,但是是浅拷贝(就像memcopy一样),但是由于都是值拷贝(浅拷贝),对于有动态开辟像栈这样的内置类型的成员,就会出现浅拷贝的问题(free两次并且公用一块空间)要用深拷贝去解决(后面讲)

那要是有数组可以吗,其实是可以的,虽然也是浅拷贝,浅拷贝就是单纯去拷贝数据。如果成员是int,int a[10],其实都可以认为是拷贝拷贝的空间和里面的数据,是开辟在不一样地方的(而且是在栈区)。拷贝int*这样指针其实也是拷贝数据,只不过里面的数据是地址,所以拷贝的时候就会两个指针指向同一块空间(堆区),free的时候就会free两次同一块空间。

拷贝构造只有自己直接管理资源的时候(像栈)才需要自己去写,自己实现深拷贝!

3.3.3 匿名对象 赋值 编译器的优化

匿名对象

用法

下面那种明显比上面那个简介(如果生成对象只是为了传参的话就可以这么用)

PS:用一个已经存在的去初始化一个创建的对象,是拷贝构造

编译器的优化(比较激进的编译器如VS)

1. 在连续的构造,拷贝构造在一起,直接优化成构造(下面这个A只有一个成员变量)

2. 连续的构造优化成构造

精题(重要!)

问调用拷贝构造多少次(单看左边,右边是左边函数的副本,为了更好看结果)

都是拷贝+拷贝优化成了拷贝

3.3.4 总结

我们不写编译器默认生成一个拷贝构造

1.内置类型成员会完成值拷贝,浅拷贝。

2.自定义类型成员,去调用这个成员的拷贝构造。

3.4. 运算符重载

3.4.1 概念

C++ 为了增强代码的可读性引入了运算符重载 运算符重载是具有特殊函数名的函数 ,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。函数名字为:关键字 operator 后面接需要重载的运算符符号 函数原型: 返回值类型  operator 操作符 ( 参数列表 )

3.4.2 注意

  1. 不能通过连接其他符号来创建新的操作符:比如operator@
  2. 重载操作符必须有一个类类型或者枚举类型的操作数
  1. 用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不 能改变其含义
  2. 作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的(this)
  3. 操作符有一个默认的形参this,限定为第一个形参
  4. .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现

3.4.3 全局的operator==

当我们的成员是私有的,但是是全局的运算符重载时,有三种解决办法

1.成员私有改为共有
2.友元
3.把运算符重载放进类里面(注意类里面会有个默认this,所以要减少一个参数)

第一种类的封装性不能保证,不行。第二种后面讲友元(friend)讲。

我们来讲第三种也是最常见的。

3.4.4 运算符重载放进类里面 

因为会有默认的this指针,所以d1是不用传的,传就错了(参数过多)。

这里需要注意的是,左操作数是this指向的调用函数的对象

3.4.5 全局和类域同名运算符重载

如果有两个相同的运算符重载一个在全局一个在类里面(是可以存在的哈,所在的作用域是不同的,前面说过类是相当于一个新的空间),那调用的时候会先调用哪个呢?

我们发现是先调用类里面的哈,也就是说编译器会现在类里面找(就近原则) 

3.4.6 赋值运算符重载 

赋值运算符主要有四点: 1. 参数类型 2. 返回值 3. 检测是否自己给自己赋值 4. 返回*this 5. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝。(浅拷贝)

上面的Date d3(d1) 改成 Date d3=d1 哈。写错了但是图不好改,因为作者是懒鬼哈哈,前面的写法是拷贝构造了哈,所以打印结果也是一样的。 

3.4.7 提问

那么编译器生成的默认赋值重载函数已经可以完成字节序的值拷贝了,我们还需要自己实现吗?答案是肯定的,因为和拷贝构造一样,都是浅拷贝,在遇到直接管理资源的时候(malloc),就需要深拷贝去解决。 

3.5 &运算符重载 and const成员

3.5.1 const修饰类的成员函数

const 修饰的类成员函数称之为 const 成员函数 const 修饰类成员函数,实际修饰该成员函数 隐含的 this 指针 ,表明在该成员函数中 不能对类的任何成员进行修改。(网图) 其实还是有一点不对,我们知道this应该是  Date *const this 此时this指向不可变 修饰之后应该是   const Date* const this 此时this指向的值也不可改变然后对于最后加上const的作用

 我们只有把Print后面加上const这样才能编过,这里涉及到的还是权限问题,如果不加上Print,就是权限的放大了,从Func的是const Date* const this 而 Print的是没有前面的const的。

注意,this前面加const修饰的是指向的对象,所以不修改this里面的内容就可以加,也推荐加上,因为这样普通对象和const对象都可以调用。

然后对于权限放大和缩小,这里有几个问题 1. const 对象可以调用非 const 成员函数吗?     不可以,权限放大 2. const 对象可以调用 const 成员函数吗?      可以,缩小 3. const 成员函数内可以调用其它的非 const 成员函数吗?    不可以,权限放大 4. const 成员函数内可以调用其它的 const 成员函数吗?    可以,缩小

3.5.2 取地址及const取地址操作符重载  

这两个默认成员函数一般不用重新定义 ,编译器默认会生成。

class Date{ public : Date* operator&() { return this ; }  const Date* operator&()const { return this ; }private : int _year ; // 年 int _month ; // 月 int _day ; // 日};

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!

class Date{ public : Date* operator&() { //return this ;   return nullptr; }  const Date* operator&()const { //return this ;   return nullptr; }private : int _year ; // 年 int _month ; // 月 int _day ; // 日};

4. 初始化列表  

对于构造函数的初始化列表 在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。

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成员变量
  • 自定义类型成员(该类没有默认构造函数)

其他的变量既可以在初始化列表初始化,也可以在函数体内初始化

总结:建议尽量在初始化列表初始化。

但是如果成员里面有自定义类型,初始化时还是会去调用其默认构造函数 

自定义类型先去走自己的默认构造函数进行初始化,然后走这里,同时这里如果不用初始化列表,就用赋值的方式(就是下面框起来的,但是要先运算符重载=)。 

用两个栈实现队列来加深理解
下面两种,都是内置类型不处理,自定义类型调用其默认构造,如果我们没有显示去写初始化列表,也会像上面那样初始化

接着 

这个(上面框起来的)是在哪初始化的呢? 

其实这个缺省值是给初始化列表用的,如果写了就不用,如果没写就用缺省值

还有很重要的一点

成员变量 在类中 声明次序 就是其在初始化列表中的 初始化顺序 ,与其在初始化列表中的先后次序无关

5. static成员 

5.1 概念

声明为 static 的类成员 称为 类的静态成员 ,用 static 修饰的 成员变量 ,称之为 静态成员变量 ;用 static 修饰 成员函数 ,称之为 静态成员函数 静态的成员变量一定要在类外进行初始化 全局普通全局变量不好,没有封装,所以C++提供了静态成员全局变量
那两者有什么区别呢?
1.静态成员变量属于整个类,属于类的所有对象,sizeof里面也不算,是放在静态区的,与之前函数放在公共代码段是不一样的  那么之前的程序就编译不通过了,因为不是在构造函数定义,是在类外定义:

5.2 访问成员函数

5.2.1 共有成员函数

但是这个方法要有对象,但是如果没有对象呢? 

5.2.2 static成员函数

这个对象也可以调,因为所有对象都用这个static成员函数

还可以类域直接调:

因为他没有this,所以可以指定类域去调
但是如果静态函数里面有非静态成员是不能访问的,因为没有this指针

但是可以创建对象然后去访问,因为这样就有this了

6. C++11 的成员初始化新玩法

概念

C++11 支持非静态成员变量在声明时进行初始化赋值, 但是要注意这里不是初始化,这里是给声明的成员变 量缺省值

 

示例

上面就可以转换成下面那样:

但是静态的不会在初始化列表初始化,还是要在全局给 

7. 友元  

7.1 友元函数

友元分为: 友元函数 友元类 友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用 就我们的日期类而言(作者忘记写博客了后面有时间了发) 问题:现在我们尝试去重载 operator<< ,然后发现我们没办法将 operator<< 重载成成员函数。 因为 cout 输出流对象和隐含的 this 指针在抢占第一个参数的位置 this 指针默认是第一个参数也就是左操作数了。但是实际使用中cout 需要是第一个形参对象,才能正常使用。所以我们要将 operator<< 重载成全局函数。但是这样的话,又会导致类外没办法访问成员,那么这里就需要友元来解决。operator>> 同理。 (cout是ostream   cin是istream) 友元函数 可以 直接访问 类的 私有 成员,它是 定义在类外部 普通函数 ,不属于任何类,但需要在类的内部声明,声明时需要加friend 关键字。

7.2 注意

  • 友元函数可访问类的私有和保护成员,但不是类的成员函数
  • 友元函数不能用const修饰(因为没有this)
  • 友元函数可以在类定义的任何地方声明不受类访问限定符限制
  • 一个函数可以是多个类的友元函数
  • 友元函数的调用与普通函数的调用和原理相同

7.3 友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。  注意:

  • 友元关系是单向的,不具有交换性。 比如有Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time 类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
  • 友元关系不能传递。如果BA的友元,CB的友元,则不能说明CA的友元。

8. 内部类

8.1 概念

如果一个类定义在另一个类的内部,这个内部类就叫做内部类。注意此时这个内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去调用内部类。外部类对内部类没有任何优越的访问权限。

注意: 内部类就是外部类的友元类。注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。 特性: 1. 内部类可以定义在外部类的 public protected private 都是可以的。 2. 注意内部类可以直接访问外部类中的 static 、枚举成员,不需要外部类的对象 / 类名 3. sizeof( 外部类)= 外部类,和内部类没有任何关系。

最后的最后,创作不易,希望读者三连支持💖

赠人玫瑰,手有余香💖