前言:本文主要介绍有关C++入门需掌握的基础知识,包括但不限于以下几个方面,这里是文章导图:
本文较长,内容较多,大家可以根据需求跳转到自己感兴趣的部分,希望能对读者有一些帮助
那么本文也主要以导图为思路进行分享,话不多说,让我们开始吧。
一、关于类的理解与意义
1、理解
什么是类呢?可以直接根据其表面来理解。这个“类”我们常说的给事物分类的“类”,比如,动物类,交通工具类等等。通过给不同的事物进行分类,能让我们更有条理性、更系统地认知这个世界。同样的道理,类的设计可以让我们的代码编写更具逻辑性、可维护性等等。以上就是对类的简单理解。
2、意义
那么类的设计有什么意义呢?基本的意义上述也提到了,能增强代码的可维护性等,它能将具有协同功能代码封装成一个整体,从而更好地进行管理;更多对其意义的感知还是需要通过不断的学习,和代码实践来逐步加强。
关于类和对象的更形象理解方式大家感兴趣地可以去搜搜文章和书籍,本文主要还是以学习笔记的方式来展现知识内容,这里就不再赘述。
二、类的定义方式
1、关于兼容C后的结构体
由于C++兼容C,所以在C++中结构体被升级成了类。在C中的结构体只能定义成员变量,升级成类后也可以定义成员函数。但二者仍有一些区别,下文会继续进行说明。
2、定义方式
类的定义方式如下:
class typename
{
};
其中class为定义类的关键字;typename为用于定义该类的类名;花括号中为类的具体内容;最后别忘了分号。
在实际工程中定义类时,我们可以根据需求将类声明(主要是其中成员函数的声明)放在头文件中,在.cpp文件中再对类中的成员函数进行定义。
三、类的访问限定符及封装的理解
类访问限定符是C++实现封装的主要方式。通过访问限定符,可以选择性地将类中的成员展现给外界用户。和平常被我们使用的电子产品一样,都是将简单易用的功能展现给我们,而将复杂的实现细节封装起来。
1.类的访问限定符
(1)类的访问限定符一共有三种,分别是:public
、protected
、private
对以上三种访问限定符的说明如下:
- 被pubilc修饰的成员能在类外直接访问
- 被protected和private修饰的成员不能在类外直接访问,(PS:关于protected和private的区别在继承章节会提到,平常使用可认为二者类似)
- 访问限定符的作用范围是:从当前访问限定符开始到下一个访问限定符出现;若下一个访问限定符一直未出现,则右花括号为止。
- class关键字定义的类默认的访问权限为private;struct关键字定义的类的默认范围权限为public(兼容C)
(2)类访问限定符在程序生命周期中作用的阶段:编译阶段。即访问限定符只在编译阶段有用,当数据映射到内存后,没有任何访问限定符上的区别。
2、对封装的理解
封装是面向对象的三大特性之一(封装、继承、多态)。对封装的理解其实很简单,前文也有谈到,其实就是将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,对外仅公开使用工具(接口)来和对象进行交互。本质上属于一种管理,让用户更方便的使用类,也让类的设计更加规范化。
四、类的作用域与实例化
1、类的作用域
类的作用域也属于“域”的一种,至此我们所认识的“域”,一共就有如下三种:
- 局部作用域
- 全局作用域
- 命名空间域
- 类作用域
在笔者的另一篇文章【逐步剖C++】-第一章-C++入门知识的命名空间部分中,提到了用域访问符::
访问命名空间,这里我们也有该符号的使用需求,请看:
在类外定义成员函数时,需使用域访问符指明成员函数属于哪个类域,请看下面代码例:
class test
{
public:
void testFunc();
}
void test::testFunc()
{
//...
}
2、类的实例化
类的实例化,本质就是通过类创建一个对象,实例化出来的对象占实际的物理空间,存储类的成员变量。由此可见,类在没有实例化之前,系统并没有为其成员变量并开实际的空间,在这个阶段成员变量仅是声明;当进行类的实例化时,成员变量才得到整体定义。
这里有一个形象的理解方式:
类的实例化过程就像拿着建筑设计图造出房子的过程。类就好比设计图,仅对房子中的内容、布局等进行规划与设计,并没有实体的建筑存在。所以,仅有一个类的定义本质是不占空间的,只有实例化出对象后,才占物理空间而实际存储数据;同理也不能直接在类外对类中声明的成员变量进行赋值,就好比设计图中不能直接住人。
五、类对象的大小计算
这里通过三个类的例子进行对类对象的大小计算的说明:
// 类中既有成员变量,又有成员函数
class A1 {
public:
void f1(){}
private:
int _a;
};
// 类中仅有成员函数
class A2 {
public:
void f2() {}
};
// 类中什么都没有---空类
class A3
{};
上面三个类实例化出的对象的大小分别为4字节、1字节、1字节。
解释:
在类中,对于成员变量和成员函数的大小计算不同:
- 对于成员变量,相同类实例化出的每个对象的成员变量是不一样的,就好比同一张设计图造出来的房子都有卧室,但这两个卧室一定是不同的;计算它们的大小时,和结构体一样,采用内存对齐的方法进行计算即可。关于内存对齐,可以看看笔者这篇文章:【逐步剖C】-第十章-自定义类型之结构体、枚举、联合
- 对于成员函数,相同类实例化出的每个对象其实是一样的,成员函数就像是篮球场、健身房等场所,不用每家都建而建成公共的,这样就避免了由于重复而引起的空间浪费,那么类中的成员函数存储方式类似,其会以成员函数表的形式放在一个公共的代码段中,当类的对象需要调用成员函数时,就到这个公共代码段中找对应的函数地址来完成调用,其实际不在类中(不占空间)。
验证:
class A
{
public:
void Test()
{
cout << "void Test()" << endl;
cout << _a << endl;
}
private:
int _a = 0; //成员变量缺省值,后续章节会说明
};
int main()
{
A a1;
A a2;
a1.Test();
a2.Test();
}
运行后转到反汇编:
可以看到,同一个类的两个不同对象所调用的成员函数的地址相同(PS:图中红框就是代码中的Test函数,蓝框是A类的默认构造函数,后续章节会进行说明)。
那么在最开始的代码例中,类A1的大小即为其成员变量的大小;类A2和类A3的大小类似,编译器会给他们一个字节来唯一标识这个类的对象。
六、this指针
1、this指针的本质
场景引入:在上面的例子中,我们通过类A实例化了两个对象,并且这两个对象都调用了成员函数Test()
,那么编译器是如何知道是哪个对象调用的呢?这就是this指针解决的问题了。
C++编译器给每个非静态的成员函数增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),指针中的内容即为对象本身的地址,在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
即例子中两条调用语句其实可理解为:
a1.Test(&a1);
a2.Test(&a2);
需要注意的是,如上仅作为一种理解方式,实际不能这么写,因为编译器规定this指针不能在实参和形参显示传递,仅可以在成员函数内部显示使用。
如:
class A
{
public:
void Test()
{
cout << "void Test()" << endl;
cout << _a << endl; //编译器自动处理this指针
//也可显示使用写为:cout << this->_a << endl;
}
private:
int _a = 0; //成员变量缺省值,后续章节会说明
};
2、this指针的特性
- this指针的类型其实为
const 类的类型*
,如上A类的对象的this指针类型就为A* const
,即不能改变指针所指向的对象,而可以改变对象中的具体内容(成员变量等); - 只能在成员函数中显示调用;
- this指针本质上是成员函数的形参,当对象调用成员函数时,编译器一般通过ecx寄存器自动将对象地址作为实参传递给该形参。故在类的对象中不存储this指针;
3、关于this指针的一些问题
了解了this指针的特性后,下面有一些常被提起的关于this指针的问题,请看:
- this指针可以为空吗?
可以通过下面这一段代码来认识并解决这个问题:
class A
{
public:
void Print()
{
cout << "Print()" << endl;
}
void PrintA()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
p->PrintA();
return 0;
}
p是一个A类对象的指针,将其赋值为空指针后用以模仿this指针来调用A类中的成员函数。运行结果如下:
可以看到,第一个函数Print()正常调用,在调用第二个函数时系统崩溃。原因很明显:p
是一个空指针,在调用第一个成员函数时,本质上并没有访问该指针所指向位置的内存(对象的具体内容);而在调用第二个成员函数时,程序想访问并输出类的成员变量_a
,那么此时的行为就相当于解引用一个空指针而去访问其指向的内容,也就是我们常说的野指针问题,故造成了程序的崩溃。
综上,this指针一般情况下不会为空。
- this指针存储在哪里?
第二个问题其实在介绍this指针的特性时已给出了答案,this指针本质上是成员函数的形参,那么既然是形参,也就是局部变量,那就一定存储在栈区当中。
本章完。
看完觉得有觉得帮助的话不妨点赞收藏鼓励一下,有疑问或有误地方的地方还请过路的朋友们留个评论,多多指点,谢谢朋友们!🌹🌹🌹