目录
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/23a79964073067a53f028553f7d6f093.jpeg#pic_center)
1.面向过程与面向对象
C语言是面向过程的,注重求解问题的步骤,通过函数调用逐步解决问题。
C++面向对象,将事情拆解为多个对象,再靠对象之间的交互完成。
举个不恰当的比方,将实现一个工程比作“造桥”的话,C语言“造桥”就好比当场现做混凝土再浇筑,而C++则先在工厂完成结构件预浇筑(制作积木——封装),再搬运到现场,而现场的工人只需要思考如何搭积木,而不用去了解积木的制作流程。
2.类的引入
C语言中我们熟悉结构体struct
,我们可以在其中定义变量,这其中包括基本类型(int,char,double…)和自定义类型(struct,union,…)
struct Date
{
int year;
int month;
int day;
};
然而在C++中,结构体不仅可以定义变量,还可以定义函数
struct Date
{
void print()
{
cout<<year<<endl;
cout<<month<<endl;
cout<<day<<endl;
}
int year;
int month;
int day;
};
int main()
{
Date d1;
d1.print();
return 0;
}
上面结构体的定义,C++中更偏向用class来代替struct关键字。
3.类的定义
class classname
{
//成员变量——描述这个类的属性
//成员函数——描述这个类的行为,也称为方法。
};//莫忘末尾的分号
class
为定义类的关键字,className
为类的名字,{}
中为类的主体,注意类定义结束时后面分号;
。
类中的元素称为类的成员:类中的数据称为类的属性或者成员变量; 类中的函数称为类的方法或者成员函数。
类的两种定义方式:
-
将成员变量和成员函数全都定义在类体中,这里需要注意定义在类体中的函数可能会被编译器当成内联函数处理。
-
更普遍的做法是将定义和声明分离。声明放在.h文件中,定义放在.cpp文件中
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类外定义成员,需要使用作用域符::
来指定成员属于哪一个类域。
4.类的访问限定符
通过访问权限的限定,可以选择性的将函数接口提供给外部用户使用
-
public修饰的成员可以在类外访问
-
protected和private修饰的成员无法在类外直接访问
-
访问限定符的作用域:从限定符的开始位置到下一个限定符出现的位置或结尾
-
在没有写访问限定符的情况下,class默认的访问权限是private,struct为public
于是我们可以为函数提供封装,将必要的数据用private/protected修饰保存而不暴露给用户修改,开放一些公有的成员函数对成员进行合理访问。封装的本质是为了更好的管理。
应用:
class Date
{
public:
void print()
{
cout<<year<<endl;
cout<<month<<endl;
cout<<day<<endl;
}
private:
int year;
int month;
int day;
};
int main()
{
Date d1;
d1.print();
//d1.day++;//错误,类外无权限访问private
return 0;
}
private修饰的变量我们只能通过类内访问,类外是无法访问的!
5.类与对象
5.1 类的实例化
我们创建的类就如同设计一张图纸,定义一个类并不会分配实际的内存空间。
一个类好比一张“建筑设计图纸”,只是设计并没有实体的建筑存在。而经过设计图纸造出的房屋的过程我们称为实例化,一个个“房屋”我们称之为对象。
实例化出的对象才能实际存储数据,占用内存空间。
5.2.类的大小计算
类中既包含成员变量,又包含成员函数,那实例化后的对象中包含了什么呢?以及如何计算一个对象的大小?
我们会将成员变量存储在对象中,因为每个对象可以具备不同的属性,例如上文中我们创建的Date类,针对对象d1,我们可以为d1设定属性(某日日期):
但是成员函数对于多个对象来说是通用的,打印日期这件事每个对象都可以去做:
试想下,如果将函数代码段也保存在每个对象中,那么相同的代码会保存多次,浪费空间。于是我们选择只保存成员变量,成员函数放在公共代码段。
我们对不同的对象分别获取大小
//空类
class A
{};
//类中仅有成员函数
class B
{
public:
void f()
{}
};
//有成员函数和成员变量
class C
{
public:
void f()
{
}
private:
int c;
};
结论:一个类的大小就是该类中的成员变量之和,当然需要注重内存对齐。注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类。
5.3内存对齐规则
- 第一个成员在与结构体偏移量为0的地址处。
- 其他成员变量要对齐到对齐数的整数倍的地址处。
注意:对齐数 = min(编译器默认的一个对齐数 , 该成员字节数)。
VS中默认的对齐数为8
修改默认对齐数
#pragma pack(8)
,将默认对齐数改为8字节
- 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
结构体大小的计算
6. this指针
6.1 this指针引出
我们先来设定一个类:Date,来设定日期:
class Date
{
public:
void SetDate(int year,int month,int day)
{
year=year;
month=month;
day=day;
}
void Print()
{
cout << year << "-" << month << "-" << day << endl;
}
private:
int year;
int month;
int day;
};
int main()
{
Date d1;
d1.SetDate(2021,12,5);
return 0;
}
这里我们的初衷是想为对象d1设定具体日期并打印,现在运行下看是否能成功
这里显然没有将函数值成功赋给d1的成员变量,导致输出的仍是未初始化的随机成员变量。
问题就出在这里:
显然这里的函数是自己的形参对自己赋值了(作用域的就近原则),为了避免歧义,我们有几个办法可以防止冲突:
- 给成员函数的成员变量添加类作用域:
但是这么做的成本太高了,每个类各自为政,类名长了就比较麻烦。
- 成员变量换一个名字,通常是在成员变量名前添加下划线
_
这里就有一个疑问了,上文我们提到过对象的存储空间是该对象成员变量所占的存储空间,而不包括函数代码所占的存储空间。 因为针对某个类的多个对象而言,不同的只是成员变量,成员函数都是一样的代码,没有必要为每个对象都保存一份成员函数的代码。
既然多个对象是共用一个成员函数的,那么函数究竟是如何区分是哪一个对象调用的呢?
是this
指针!
C++通过this指针解决了这个问题,C++编译器给每一个“非静态成员函数”添加一个隐藏的类指针参数this
,该指针指向当前调用函数的对象,在函数中对所有成员变量的操作都是利用this指针进行访问的。这个隐含的参数,用户是看不见的,由编译器自动完成。
于是我们有了第三个方法防止形参名与成员变量名冲突的问题:
- 在成员名前添加this指针
6.2 理解this指针
成员函数最终被编译成与对象无关的普通函数,除了成员变量,会丢失所有信息,所以编译时要在成员函数中添加一个额外的参数,把当前对象的首地址传入,以此来关联成员函数和成员变量。这个额外的参数,实际上就是 this,它是成员函数和成员变量关联的桥梁。
- this指针的类型:
类类型* const
- 只能在成员函数的内部使用
- this指针实际上是成员函数的形参,在对象调用成员函数时,将对象地址作为实参传递给this形参,所以对象不存储this指针。
- this指针是成员函数的第一个隐含的指针形参,其实编译器在生成程序时加入了获取对象首地址的相关代码。并把获取的首地址存放在了寄存器ECX中(vs是放在ECX中,其它编译器有可能不同)。
也就是成员函数的其它参数正常都是存放在栈中。而this指针参数则是存放在寄存器中。类的静态成员函数因为没有this指针这个参数,所以类的静态成员函数也就无法调用类的非静态成员变量。
可以验证下this指针和对象的首地址是否一致
- 那 this指针是否可以为空呢?
看下面这段代码
class A
{
public:
void PrintA()
{
cout<<_a<<endl;
}
void Show()
{
cout<<"Show()"<<endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
//分别执行下面两行代码
p->PrintA();//1.this为nullptr 调用PrintA()
p->Show();//2.this为nullptr 调用Show()
}
p为空指针,->
对p执行了解引用操作。
执行1.
执行2.
由此得到
this指针可以为空,当我们调用函数时,如果函数内部不需要使用到this,也就是不需要通过this指向当前对象并对其进行操作时才可以为空,如果调用的函数需要指向当前对象,并进行操作,则会发生错误(空指针引用)就跟C中一样不能进行空指针的引用。
青山不改 绿水长流