1.类的引入
2.类的定义方式
3.访问权限
4.封装
5.类也是作用域
6.类的实例化
7.如何求一个类的大小
8.this指针
9.默认成员函数
10.构造函数
11.析构函数
12.拷贝构造函数
13.赋值运算符重载
14.const的类成员
15初始化列表
16.static的类成员
17.友元
18.内部类
C语言结构体中只能定义变量,但是C++可以在结构体中定义变量和定义函数
Class关键字加 类名 Class 类名 {}; 在C++中class和struct都可以定义类 第一种方式 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。 第二种方式 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数前需要加类名
成员能否在类外或者子类中被使用 private 私有的 protected 保护的 public 公有的 这里struct定义类就是公有的 而class定义的类就是私有的 访问限定符只在编译时有用
4.1什么是封装?
将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。说人话就是你给一个实现某一功能的类进行一次封装以后再需要实现某种功能的时候直接调用这个类就行了
4.2C++是如何进行封装的?
通过类将数据与操作数据的方式结合在一起,用来描述对象,更符合人的认知,搭配上访问权限。
5.1为什么说类也是作用域呢?
成员函数在类外定义时,函数名字前需要添加类名::成员函数名字
5.2谈谈作用域
1.函数体内部的局部作用域
在函数内部就是局部作用域,这个代码的名字只在函数的内部起作用,调用函数时创建函数作用域,函数执行完毕之后,函数作用域销毁,每调用一次函数就会创建一个新的函数作用域,它们之间是相互独立的。
2.全局作用域
全局变量具有全局作用域。全局变量只需在一个源文件中定义,就可以作用于所有的源文件。当然,其他不包含全局变量定义的源文件需要用extern 关键字再次声明这个全局变量。
3.命名空间
一个命名空间确定一个命名空间作用域,凡是在该命名空间之内声明的,不属于前面所描述的各个作用域的标识符,都属于该命名空间的作用域。在命名空间内部可以直接引用当前命名空间的标识符。作用域关系: 命名空间作用域>类作用域>局部作用域
4.类域
简单来说,类域就是定义在类里面的变量;类方法是定义在类里面的函数。
6.1什么是类的实例化?
简单理解,可以把我们生活中的一些模具当作类,那么根据模具制作出来的具体的事物就是对象,模具定义了这类对象的属性和行为,创建对象的过程就叫做类的实例化。
6.2声明、初始化、实例化的区别
声明:只生成对象不赋值的过程 初始化:是给对象赋值的过程 实例化:是使用new给对象分配内存的过程
6.3类和对象的区分
1.类是用来描述对象的
2.对象是真实存在的,里面会放数据,对象类似于用内置类型定义出来的变量,类是一个静态的概念,类本身不携带任何数据。当没有为类创建任何对象时,类本身不存在于内存空间中。
3.类是一个抽象的概念,它不存在于现实中的时间/空间里,类只是为所有的对象定义了抽象的属性与行为。就好像“Person(人)”这个类,它虽然可以包含很多个体,但它本身不存在于现实世界上。
4.对象是一个动态的概念。每一个对象都存在着有别于其它对象的属于自己的独特的属性和行为。对象的属性可以随着它自己的行为而发生改变。
7.1对象模型
有两个概念可以解释C++对象模型: 语言中直接支持面向对象程序设计的部分。 对于各种支持的底层实现机制。比如之后的虚表指针,多继承我们使用对象模型可以便于理解。
7.2对象中包含什么?
对象中只存储非静态的成员变量,成员函数没有存储在对象中,存储在代码段。
7.3计算类(对象)大小
将非静态的成员变量相加;注意内存对齐。注意求类对象的大小和求结构体的大小是类似的。
为什么要内存对齐?
字节对齐主要是为了提高内存的访问效率,比如intel 32位cpu,每个总线周期都是从偶地址开始读取32位的内存数据,如果数据存放地址不是从偶数开始,则可能出现需要两个总线周期才能读取到想要的数据,因此需要在内存中存放数据时进行对齐。
内存对齐规则:
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.1什么是this指针?
C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),里面放置的是当前对象的地址,在函数体中所有“成员变量” 的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。在类的非静态成员函数中返回对象本身,可使用return *this。
8.2this指针的特性
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.1在类中,用户没有显式实现,则编译器会生成一份成员函数
语法说没有定义则生成,实际情况却不一定,因为编译器要考虑程序运行效率问题,因此对于没有意义的默认成员函数则不生成,什么情况下会生成,取决于编译器是否需要,如果编译器感觉到自己需要则生成,自己不需要则不生成,
9.2默认成员函数有哪些?
9.2.1C++98:构造函数,拷贝构造函数,复制运算符重载,析构函数,T* operator&*(),const T* operator&*()const。
9.2.2C++11:移动构造: T(T&&),移动赋值:T& operator=(T&&)
10.1什么是构造函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
10.2构造函数的特性
10.3构造函数的使用场景
创建类类型对象时,由编译器自动调用,给对象设置初始值。
10.4关于编译器生成的默认构造函数
A类中包含B类的对象,B类定义了默认构造函数(无参构造函数 或 全缺省构造函数)如果被包含类对象的类中没有定义默认构造函数,则编译器不会生成默认构造函数
基类带有默认构造函数(无参构造函数 或 全缺省构造函数),创建派生类对象时(类A是基类,类B是派生类,类B继承自类A,类A中带有默认构造函数)。
类中含有虚函数,编译器一定会给该类生成默认的构造函数。
在虚拟继承中,编译器一定会给子类生成构造函数。
11.1什么是析构函数
11.2析构函数的特性
11.3析构函数的使用场景
在对象被销毁时由编译器自动调用,作用:将对象中的资源清理干净
栈上的对象,在函数结束时候被销毁会自动调用析构函数
堆上的对象,delete 或者 delete[]
11.4关于编译器生成的默认析构函数
类中如果没有涉及到资源管理时,析构函数是否给出无所谓;但是如果涉及到资源管理,用户必须要显式给出析构函数,在析构函数中清理对象的资源。
12.1什么是拷贝构造函数
12.2拷贝构造函数的特性
12.3拷贝构造函数的使用场景
当使用类类型对象创建新对象时候1.使用已存在对象创建新对象 2.函数参数类型为类类型对象 3.函数返回值类型为类类型对象 以值的方式传参 以值的方式返回
12.4关于编译器生成的默认拷贝构造函数
浅拷贝 如果发生浅拷贝多个对象用一个地址空间就会发生写时拷贝
当类中涉及到资源管理时,必须要自己实现拷贝构造函数 深拷贝
13.1运算符重载
13.1.1什么是运算符重载
13.1.2语法格式
返回值类型 operator运算符(参数列表){...}
13.1.3注意事项
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
13.2.2关于编译器生成的默认赋值运算符重载
如果类设计到资源管理时,需要用户显式实现 ,深拷贝
在C语言中:const只能修饰变量,表明该变量是一个不可以被修改的变量
在C++中,修饰普通变量:已经是常量,而且具有宏替换效果,发生在编译时
在C++中,修饰类成员,成员变量,该成员变量必须在初始化列表的位置进行初始化---类的构造函数必须给出;成员函数,实际是在修饰this,在该成员函数中不能对非静态成员变量进行修改,如果一定要修改,则该成员变量必须被mutable修饰。
15.1在哪能用到 他的作用是什么?
是构造函数||拷贝构造函数||移动构造才有的
作用是对类中的非静态成员变量进行初始化,因为成员函数体内部是赋值。
15.2语法格式
16.1什么是static类成员?
本质:该静态成员函数没有this指针。内部不能访问非静态成员变量,内部不能调用非静态成员函数,不能是const成员函数,不能是虚函数。
如果类的成员函数想作为回调函数来使用,一般情况下只能将它定义为静态成员才行。
普通成员函数可以访问所有成员变量,而静态成员函数只能访问静态成员变量。
调用一个对象的非静态成员函数时,系统会把当前对象的起始地址赋给 this 指针。而静态成员函数并不属于某一对象,它与任何对象都无关,因此静态成员函数没有 this 指针。
17.1什么是友元
友元是一种定义在类外部的普通函数或类,但它需要在类体内进行说明,为了与该类的成员函数加以区别,在说明时前面加以关键字friend。 友元不是成员函数,但是它可以访问类中的私有成员。 友元的作用在于提高程序的运行效率,但是,它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。
17.2友元的分类
17.2.1友元函数
声明格式
friend 类型 函数名(形参);
friend class 类名;
17.3注意事项
利用 friend 修饰符,可以让一些普通函数 或 另一个类的成员函数 直接对某个类的保护成员和私有成员进行操作,提高了程序的运行效率;同时避免把类的成员都声明为public,最大限度地保护数据成员的安全。但是,即使是最大限度地保护数据成员,友元也破坏了类的封装性。如果将类的封装比喻成一堵墙的话,那么友元机制就像墙上开了一个门。所以使用友元时一定要慎重。
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内部类特性
19.1什么是匿名对象?
匿名对象可以理解为是一个临时对象,一般系统自动生成的,如你的函数返回一个对象,这个对象在返回时会生成一个临时对象。
19.2注意事项
如果生成的匿名对象在外部有对象等待被其实例化,此匿名对象的生命周期就变成了外部对象的生命周期;如果生成的匿名对象在外面没有对象等待被其实例化,此匿名对象将会生成之后,立马被析构。
连续构造+拷贝构造->优化为直接构造
连续构造+拷贝构造->优化为一个构造
对象返回总结:接收返回值对象,尽量拷贝构造方式接收,不要赋值接收,函数中返回对象时,尽量返回匿名对象
函数传参总结:尽量使用const
+ &
传参,减少拷贝的同时防止权限放大