C++ 第二弹封装-类和对象

目录

1.类的引入

2.类的定义方式

3.访问权限

4.封装

5.类也是作用域

6.类的实例化

7.如何求一个类的大小

8.this指针

9.默认成员函数

10.构造函数

11.析构函数

12.拷贝构造函数

13.赋值运算符重载

14.const的类成员

15初始化列表

16.static的类成员

17.友元

18.内部类


1.类的引入

C语言结构体中只能定义变量,但是C++可以在结构体中定义变量和定义函数

2.类的定义方式

Class关键字加 类名    Class 类名 {};   在C++中class和struct都可以定义类                                     第一种方式 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。                                                                                                                         第二种方式 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数前需要加类名 

3.访问权限

 成员能否在类外或者子类中被使用  private 私有的  protected 保护的 public 公有的 这里struct定义类就是公有的 而class定义的类就是私有的 访问限定符只在编译时有用 

4.封装

4.1什么是封装?

将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。说人话就是你给一个实现某一功能的类进行一次封装以后再需要实现某种功能的时候直接调用这个类就行了

4.2C++是如何进行封装的?

通过类将数据与操作数据的方式结合在一起,用来描述对象,更符合人的认知,搭配上访问权限。

5.类也是作用域

5.1为什么说类也是作用域呢?

成员函数在类外定义时,函数名字前需要添加类名::成员函数名字

5.2谈谈作用域

1.函数体内部的局部作用域

在函数内部就是局部作用域,这个代码的名字只在函数的内部起作用,调用函数时创建函数作用域,函数执行完毕之后,函数作用域销毁,每调用一次函数就会创建一个新的函数作用域,它们之间是相互独立的。

2.全局作用域

全局变量具有全局作用域。全局变量只需在一个源文件中定义,就可以作用于所有的源文件。当然,其他不包含全局变量定义的源文件需要用extern 关键字再次声明这个全局变量。

3.命名空间

一个命名空间确定一个命名空间作用域,凡是在该命名空间之内声明的,不属于前面所描述的各个作用域的标识符,都属于该命名空间的作用域。在命名空间内部可以直接引用当前命名空间的标识符。作用域关系: 命名空间作用域>类作用域>局部作用域

4.类域

简单来说,类域就是定义在类里面的变量;类方法是定义在类里面的函数。 

6.类的实例化

6.1什么是类的实例化?

简单理解,可以把我们生活中的一些模具当作类,那么根据模具制作出来的具体的事物就是对象,模具定义了这类对象的属性和行为,创建对象的过程就叫做类的实例化。

6.2声明、初始化、实例化的区别

声明:只生成对象不赋值的过程 初始化:是给对象赋值的过程 实例化:是使用new给对象分配内存的过程

6.3类和对象的区分

1.类是用来描述对象的

2.对象是真实存在的,里面会放数据,对象类似于用内置类型定义出来的变量,类是一个静态的概念,类本身不携带任何数据。当没有为类创建任何对象时,类本身不存在于内存空间中。

3.类是一个抽象的概念,它不存在于现实中的时间/空间里,类只是为所有的对象定义了抽象的属性与行为。就好像“Person(人)”这个类,它虽然可以包含很多个体,但它本身不存在于现实世界上。

4.对象是一个动态的概念。每一个对象都存在着有别于其它对象的属于自己的独特的属性和行为。对象的属性可以随着它自己的行为而发生改变。

7.如何求一个类的大小

7.1对象模型

有两个概念可以解释C++对象模型: 语言中直接支持面向对象程序设计的部分。 对于各种支持的底层实现机制。比如之后的虚表指针,多继承我们使用对象模型可以便于理解。

7.2对象中包含什么?

对象中只存储非静态的成员变量,成员函数没有存储在对象中,存储在代码段。

7.3计算类(对象)大小

将非静态的成员变量相加;注意内存对齐。注意求类对象的大小和求结构体的大小是类似的。

为什么要内存对齐?

字节对齐主要是为了提高内存的访问效率,比如intel 32位cpu,每个总线周期都是从偶地址开始读取32位的内存数据,如果数据存放地址不是从偶数开始,则可能出现需要两个总线周期才能读取到想要的数据,因此需要在内存中存放数据时进行对齐。

内存对齐规则:

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

7.4空类 

7.4.1什么是空类?

空类是指这个类不带任何数据,即类中没有非静态(non-static)数据成员变量,没有虚函数(virtual function),也没有虚基类(virtual base class)。

7.4.2那空类有没有成员函数呢?

如果你只是声明一个空类,不做任何事情的话,编译器会自动为你生成一个默认构造函数、一个拷贝默认构造函数、一个默认拷贝赋值操作符、一个默认析构函数、取址运算符和一个取址运算符const。这些函数只有在第一次被调用时,才会别编译器创建。所有这些函数都是inline和public的。

7.4.3空类的大小?

空类的大小至少为1字节但是由于内存对齐编译器可能还会增加一些字节。

7.5结构体内存对齐

7.5.1什么是结构体内存对齐?

元素是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。从结构体存储的首地址开始,每个元素放置到内存中时,它都会认为内存是按照自己的大小(通常它为4或8)来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始,这就是所谓的内存对齐。

7.5.2为什么要对齐?

平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

假如没有内存对齐机制,数据可以任意存放,现在一个int变量存放在从地址1开始的联系四个字节地址中,该处理器去取数据时,要先从0地址开始读取第一个4字节块,剔除不想要的字节(0地址),然后从地址4开始读取下一个4字节块,同样剔除不要的数据(5,6,7地址),最后留下的两块数据合并放入寄存器。这需要做很多工作。

现在有了内存对齐的,int类型数据只能存放在按照对齐规则的内存中,比如说0地址开始的内存。那么现在该处理器在取数据时一次性就能将数据读出来了,而且不需要做额外的操作,提高了效率。

7.5.3怎么求结构体的大小?

1、结构体变量的首地址,必须是结构体变量中的“最大基本数据类型成员所占字节数”的整数倍(对齐)
2、结构体变量中的每个成员相对于结构体首地址的偏移量,都是该成员基本数据类型所占字节的整数倍。(对齐)
3、结构体变量的总大小,为结构体变量中“最大基本数据类型成员所占字节数”的整数倍(补齐)

7.5.4如果指定结构体按照特定的字节数对齐?

手动设置字节数对齐的方式有两种:代码里添加预编译标识# pragma pack():使用__attribute__:

7.5.6结构体可以按照任意字节对齐吗?

不可以。

7.5.7大小端概念

对于一个由2个字节组成的16位整数,在内存中存储这两个字节有两种方法:一种是将低序字节存储在起始地址,这称为小端(little-endian)字节序;另一种方法是将高序字节存储在起始地址,这称为大端(big-endian)字节序。大端是高字节存放到内存的低地址,小端是高字节存放到内存的高地址。

7.5.8如何对大小端来进行测试?

最简单的方法就是定义一个8字节的地址 强转成char 输出  看看输出的是八字节地址的前两位还是后两位 如果是前两位就是大端模式,如果是后两位就是小端模式。

7.5.9那些场景下需要考虑大小端的问题?

比如通讯发报文,如果报文数据是大端模式但是接收他的缓冲区是小端模式,如果不进行数据转换直接发送,此时就会发生灾难性的错误。

7.5.10那么我们如何进行大小端转换呢?

可以翻转字符串 位操作 

8.this指针

8.1什么是this指针?

C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),里面放置的是当前对象的地址,在函数体中所有“成员变量” 的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。在类的非静态成员函数中返回对象本身,可使用return *this。

8.2this指针的特性

1.this指针的类型:类类型*const, 即成员函数中,不能给 this 指针赋值。
2. 只能在 成员函数 ”的内部使用,在全局函数,静态成员函数中都不能使用(this指针始终指向对象,但是静态成员函数属于类)。
3. this指针本质上是成员函数的形参, 当对象调用成员函数时,将对象地址作为实参传递给
this 形参。所以对象中不存储this指针。
4. this指针是 成员函数 第一个隐含的指针形参,一般情况由编译器通过 ecx寄存器自动传
递,不需要用户传递。
5.this指针在成员函数开始前构造并在成员函数结束后清除。
6.this指针因为编译器的不同存储在不同位置
感性理解: 你在一个房子里面 你能看见桌子椅子但是你却看不到房子本身 这个房子本事就是对象本身也就是this指针指向对象本身

8.3this指针存储在哪里?

其实编译器在生成程序时加入了获取对象首地址的相关代码。并把获取的首地址存放在了寄存器ECX中(VC++编译器是放在ECX中,其它编译器有可能不同)。也就是成员函数的其它参数正常都是存放在栈中。而this指针参数则是存放在寄存器中。

8.4this指针可以为空吗?

可以是空 

当通过对象调用非静态成员函数时候,this指针一定不会为空

当通过指针调用时,this可能会为空 this为空,但是本次调用的成员函数的内部没有访问任何成员变量,则代码一定不会崩溃 this为空,但是本次调用的成员函数内部访问了非静态的成员变量,则代码一定会崩溃--因为:在成员函数中,所有非静态的成员的成员变量都是通过this指针访问的

两种理解:

1.this指针本身就是一个参数 参数当然可以传空

2.静态绑定可以为空,动态绑定不能为空

8.5类的编译过程

1.识别类名 2.识别类中成员变量 3.识别类中成员函数,并对成员函数进行修改

8.5.1怎么对成员函数进行修改?

1.还原this指针 2.成员变量访问前加this

9.默认成员

9.1在类中,用户没有显式实现,则编译器会生成一份成员函数

语法说没有定义则生成,实际情况却不一定,因为编译器要考虑程序运行效率问题,因此对于没有意义的默认成员函数则不生成,什么情况下会生成,取决于编译器是否需要,如果编译器感觉到自己需要则生成,自己不需要则不生成,

9.2默认成员函数有哪些?

9.2.1C++98:构造函数,拷贝构造函数,复制运算符重载,析构函数,T* operator&*(),const T* operator&*()const。

9.2.2C++11:移动构造: T(T&&),移动赋值:T& operator=(T&&)

10.构造函数 

10.1什么是构造函数

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

10.2构造函数的特性

1. 函数名与类名相同,无返回值。
2. 每个类可以有多个构造函数。
3. 对象实例化时编译器 自动调用对应的构造函数。
4. 构造函数可以重载,但是不能被继承,不能被覆盖。
5. 如果类中没有显式定义构造函数,则 C++编译器会自动生成一个无参的默认构造函数,一旦
用户显式定义编译器将不再生成。
6. 对象调用了编译器生成的默 认构造函数,但是 对象 的参数 依旧是随机值。
7.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
ps:普通方法也可以和类名相同。

10.3构造函数的使用场景

创建类类型对象时,由编译器自动调用,给对象设置初始值。

10.4关于编译器生成的默认构造函数

A类中包含B类的对象,B类定义了默认构造函数(无参构造函数 或 全缺省构造函数)如果被包含类对象的类中没有定义默认构造函数,则编译器不会生成默认构造函数

基类带有默认构造函数(无参构造函数 或 全缺省构造函数),创建派生类对象时(类A是基类,类B是派生类,类B继承自类A,类A中带有默认构造函数)。

类中含有虚函数,编译器一定会给该类生成默认的构造函数。

在虚拟继承中,编译器一定会给子类生成构造函数。

11.析构函数

11.1什么是析构函数

  与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由
编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。  

11.2析构函数的特性

1. 析构函数名是在类名前加上字符 ~
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构
函数不能重载
4. 对象生命周期结束时, C++ 编译系统系统自动调用析构函数

11.3析构函数的使用场景

在对象被销毁时由编译器自动调用,作用:将对象中的资源清理干净

栈上的对象,在函数结束时候被销毁会自动调用析构函数

堆上的对象,delete 或者 delete[]

11.4关于编译器生成的默认析构函数

类中如果没有涉及到资源管理时,析构函数是否给出无所谓;但是如果涉及到资源管理,用户必须要显式给出析构函数,在析构函数中清理对象的资源。

12.拷贝构造函数

12.1什么是拷贝构造函数

只有单个形参,该形参是对本 类类型对象的引用 ( 一般常用 const 修饰 ) ,在用已存
在的类类型对象创建新对象时由编译器自动调用。

12.2拷贝构造函数的特性

1. 拷贝构造函数 是构造函数的一个重载形式。
2. 拷贝构造函数的 参数只有一个 必须是类类型对象的引用 ,使用 传值方式编译器直接报错,
因为会引发无穷递归调用。
  3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按
字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。 在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定 义类型是调用其拷贝构造函数完成拷贝的。 
4.类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

12.3拷贝构造函数的使用场景

当使用类类型对象创建新对象时候1.使用已存在对象创建新对象 2.函数参数类型为类类型对象 3.函数返回值类型为类类型对象 以值的方式传参 以值的方式返回

为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用
尽量使用引用。

12.4关于编译器生成的默认拷贝构造函数

浅拷贝 如果发生浅拷贝多个对象用一个地址空间就会发生写时拷贝

当类中涉及到资源管理时,必须要自己实现拷贝构造函数 深拷贝

13.赋值运算符重载

13.1运算符重载

13.1.1什么是运算符重载

C++为了增强代码的可读性引入了运算符重载 运算符重载是具有特殊函数名的函数,也具有其
返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

13.1.2语法格式

返回值类型 operator运算符(参数列表){...}

13.1.3注意事项

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

13.2赋值运算符重载

13.2.1语法格式

Date& operator=(const Date& d)
{
     if(this != &d)
    {
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
    return *this;
}

1.为什么要加&

加上&一是为了减少拷贝,二是为了防止引发无限拷贝

2.为什么要加const

加const是为了能让参数也可以接收右值,也就是说假如不加const,实参如果是.const类型的,就调用不了(权限放大),const引用传参,这实际上是一个万能引用,能接收左值和右值

3.为什么要有返回值

为了支持连续赋值

4.为什么要以引用的方式返回

引用返回值,是为了实现连续赋值,同时传引用返回减少调用拷贝构造。一定程度上提高性能。

5.为什么要检测this!=&d

检测是否自己给自己赋值

6.为什么要返回*this

这里我们返回值就是一个自定义类型,我们在拷贝构造里面对这些成员变量的修改,其实都是对this指针指向的对象的各个变量的修改,而*this就可以表示这个结构体对象
13.2.3注意事项
赋值运算符只能重载成类的成员函数不能重载成全局函数, 赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现 一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值 运算符重载只能是类的成员函数。
 

13.2.2关于编译器生成的默认赋值运算符重载

用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝 。注
意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符
重载完成赋值。 浅拷贝

如果类设计到资源管理时,需要用户显式实现 ,深拷贝

如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必
须要实现。

14.const的类成员

const修饰类成员函数,实际修饰该成员函数 隐含的this指针 ,表明在该成员函数中不能对类的任何成员进行修改。

在C语言中:const只能修饰变量,表明该变量是一个不可以被修改的变量

在C++中,修饰普通变量:已经是常量,而且具有宏替换效果,发生在编译时

在C++中,修饰类成员,成员变量,该成员变量必须在初始化列表的位置进行初始化---类的构造函数必须给出;成员函数,实际是在修饰this,在该成员函数中不能对非静态成员变量进行修改,如果一定要修改,则该成员变量必须被mutable修饰。

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

15初始化列表

15.1在哪能用到 他的作用是什么?

是构造函数||拷贝构造函数||移动构造才有的

作用是对类中的非静态成员变量进行初始化,因为成员函数体内部是赋值。

15.2语法格式

以一个冒号开始 ,接着是一个以 逗号分隔的数据成员列表 ,每个 " 成员变量 "后面跟
一个放在括号中的初始值或表达式。
15.3那些成员必须要在初始化列表中初始化
1.类中包含引用类型的成员变量
2.类中包含const类型的成员变量
3.类中包含了其他类类型对象,并且该类具有带参的构造函数
15.4注意事项 
  尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,
一定会先使用初始化列表初始化。
成员变量在类中 声明次序 就是其在初始化列表中的 初始化顺序,与其在初始化列表中的先后
次序无关
对于类类型成员会自动在初始化列表中调用无参的构造函数,在构造函数体里面其实只能对其进行赋值了
在初始化列表中可以调用指定的构造函数来初始化类类型的成员变量,任意一个构造函数都会默认调用类类型的无参构造函数
explicit 修饰构造函数,将会禁止构造函数的隐式转换

16.static的类成员

16.1什么是static类成员?

声明为static 的类成员 称为 类的静态成员 ,用 static 修饰的 成员变量 ,称之为 静态成员变量;用
static修饰 成员函数 ,称之为 静态成员函数 。静态成员变量一定要在类外进行初始化
16.2static关键字
在C语言中可以修饰变量也可以修饰函数 如果是修饰函数那么该函数只能在当前文件中使用---修改了其链接属性
在C++中可以修饰类成员,静态成员变量和静态成员函数
16.2.1静态成员变量和普通成员变量的区别
1.静态成员变量是属于整个类的全局变量,它不单属于某一个对象,初始化时不受private和protected访问限制,static成员变量不占用对象内存,在所有对象外开辟内存,不创建对象也可以访问,静态成员变量可以被自己所在类派生类的对象共享;普通成员变量是属于类中的对象,它不能被所有对象共享。
2.静态成员变量必须在全局进行定义,而由于普通成员变量在创建一个对象的时候可以对它进行初始化,因此就不需要单独对它进行定义,只要声明即可。
3.static成员变量和普通static变量一样,编译时在静态数据区分配内存,到程序结束时才释放。这就意味着static成员变量不随对象的创建而分配内存,也不随对象的销毁而释放内存。而普通成员变量在对象创建时分配内存,在对象销毁时释放内存。 静态成员变量从类被加载开始到类被卸载,一直存在。普通成员变量只有在类创建对象后才开始存在,对象结束,它的生命也就告终。
4.static成员变量初始化不赋值会被默认初始化,一般是 0。静态数据区的变量都有默认的初始值,而动态数据区(堆区、栈区)的变量默认是垃圾值。
5.静态成员变量可以成为成员函数的可选参数,而普通成员变量不可以。
6.静态成员变量的类型可以是所属类的类型,但是普通成员变量不行。普通成员只能声明为所属类类型的指针或引用。
7.静态成员也是类的成员,受 public protected private 访问限定符的限制
8.静态成员变量必须在 类外定义 ,定义时不添加 static 关键字,类中只是声明
9.类静态成员即可用 类名:: 静态成员 或者 对象 . 静态成员 来访问
16.2.2静态成员函数

本质:该静态成员函数没有this指针。内部不能访问非静态成员变量,内部不能调用非静态成员函数,不能是const成员函数,不能是虚函数。

如果类的成员函数想作为回调函数来使用,一般情况下只能将它定义为静态成员才行。

普通成员函数可以访问所有成员变量,而静态成员函数只能访问静态成员变量。

调用一个对象的非静态成员函数时,系统会把当前对象的起始地址赋给 this 指针。而静态成员函数并不属于某一对象,它与任何对象都无关,因此静态成员函数没有 this 指针。

静态成员函数与非静态成员函数的根本区别是:有无this指针。由此决定了静态成员函数不能访问本类中的非静态成员。

17.友元

17.1什么是友元

友元是一种定义在类外部的普通函数或类,但它需要在类体内进行说明,为了与该类的成员函数加以区别,在说明时前面加以关键字friend。 友元不是成员函数,但是它可以访问类中的私有成员。 友元的作用在于提高程序的运行效率,但是,它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。

17.2友元的分类

17.2.1友元函数

声明格式

friend 类型 函数名(形参);
1.友元函数 可访问类的私有和保护成员,但 不是类的成员函数
2.友元函数 不能用 const 修饰
3.友元函数 可以在类定义的任何地方声明, 不受类访问限定符限制
4.一个函数可以是多个类的友元函数
5.友元函数的调用与普通函数的调用原理相同
17.2.2友元类
声明格式
friend class 类名;
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
友元关系是单向的,不具有交换性。
如果 C B 的友元, B A 的友元,则不能说明 C A 的友元。
友元关系不能继承。

17.3注意事项

利用 friend 修饰符,可以让一些普通函数 或 另一个类的成员函数 直接对某个类的保护成员和私有成员进行操作,提高了程序的运行效率;同时避免把类的成员都声明为public,最大限度地保护数据成员的安全。但是,即使是最大限度地保护数据成员,友元也破坏了类的封装性。如果将类的封装比喻成一堵墙的话,那么友元机制就像墙上开了一个门。所以使用友元时一定要慎重。

18.内部类

18.1什么是内部类

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

18.2语法格式

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
 }
 };
};

18.3内部类特性

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

19.匿名对象

19.1什么是匿名对象?

匿名对象可以理解为是一个临时对象,一般系统自动生成的,如你的函数返回一个对象,这个对象在返回时会生成一个临时对象。

19.2注意事项

如果生成的匿名对象在外部有对象等待被其实例化,此匿名对象的生命周期就变成了外部对象的生命周期;如果生成的匿名对象在外面没有对象等待被其实例化,此匿名对象将会生成之后,立马被析构。

20.拷贝对象时的一些编译器优化

连续构造+拷贝构造->优化为直接构造

连续构造+拷贝构造->优化为一个构造

连续拷贝构造 + 拷贝构造 -> 优化一个拷贝构造
连续拷贝构造 + 赋值重载 -> 无法优化

对象返回总结:接收返回值对象,尽量拷贝构造方式接收,不要赋值接收,函数中返回对象时,尽量返回匿名对象

函数传参总结:尽量使用const + &传参,减少拷贝的同时防止权限放大

21.再次理解类和对象

类是对某一类实体 ( 对象 ) 来进行描述的,描述该对象具有那些属性,那些方法,描述完成后就形成了一种新的自定义类型,才用该自定义类型就可以实例化 具体的对象。

你可能感兴趣的:(c++,开发语言)