前言
从本文开始,笔者将开始分享自己学习C++面向对象的过程,由于面向对象是C++很重要的一环,因此笔者将分为三次来介绍,源码可以从专栏介绍中的GitHub或gitee自取。
1.类的定义
在C语言学习过程中,我们都知道不能重复定义同一个名称的函数,比如栈和队列都有Push函数,但是我们必须定义两个不同名字的函数,为了解决此类情况,C++中提出了类。
<1>类定义的格式
- class是定义类的关键字,在其后面加上类的名字,类定义结束时后面的分号不能省略,类中成员称作成员变量,类中的函数称作成员函数。
- 定义在类内的函数默认为inline
- 由于成员变量需要和形参区分,因此常常的成员变量前一个_
- C++中同样支持struct,但是在C++中struct升级成了类,也就是说在C++里struct既可以作为结构体,也可以作为一个类。
<2>访问限定符
- 访问限定符其实就是C++实现封装的方式,用类将对象属性与方法结合,然后通过设置访问权限来提供接口给外部使用
- public
public修饰的成员可以在类外直接访问 - private
private修饰的成员不能直接在外部访问,protected和privat在这里是一样的,具体的区别只有在继承部分才体现 - class与struct
class和struct作为类的关键字的唯一区别就是class定义成员默认为privata,struct定义成员默认为public。
<3>类域
这个点比较简单,我们之前就学过全局域,以及自己定义的域,而每个类其实就定义了一个新的作用域,因此类外定义成员时,需要使用::指明。
<代码示例>
// class Date
struct Date
{
public:
void Init(int year, int month, int day);
private:
int _year; // 特殊标记
int _month;
int _day;
};
void Date::Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
// 兼容C的struct用法
typedef struct ListNodeC
{
int val;
struct ListNodeC* next;
}LTNodeC;
// CPP
struct ListNodeCPP
{
int val;
ListNodeCPP* next;
};
int main()
{
Date d2;
Date d3;
d2.Init(2024, 7, 9);
struct Date d1;
ListNodeCPP node;
//Date::_year = 2024;
return 0;
}
2.实例化
<1>实例化概念
首先先理解实例化,其实实例化是一个很简单的概念,如果把类看作一张图纸,那么实例化就是按照图纸做出了具体的物品,也就是说类是抽象的,类的实例化才是实际分配空间的。一个类也可以实例化多个对象。
<2>对象大小
通过刚才的学习,我们知道了实例化的的d1,d2是两个对象,各自由他们独立的变量,存储各自的数据,不过如果二者都调用Print这个函数,通过反汇编,不难发现存储的是成员函数的指针,不过其实函数指针是无需存储的,因为调用函数的时候会被编译成汇编指令call地址,编译器在编译链接时就要找到函数地址。
3.this指针
最后,我们来学习一下C++语法中的重要知识点——this指针。首先我们先来思考一个问题:Data类中有成员函数,但是函数体并没有不同对象的区分,那么函数如何知道应该访问的是d1还是d2,其实这就是this指针起了作用.类的成员函数第一个位置默认增加一个当前类型的指针,但是要记住C++规定实参和形参不能显示写出this指针,但是函数体内部可以使用。
<代码示例>
#include<iostream>
using namespace std;
class Date
{
public:
// void Init(Date* const this, int year, int month, int day)
void Init(int year, int month, int day)
{
// 编译报错:error C2106: “=”: 左操作数必须为左值
// this = nullptr;
// this->_year = year;
_year = year;
this->_month = month;
this->_day = day;
}
void Print()
19{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
// 这⾥只是声明,没有开空间
int _year;
int _month;
int _day;
};
int main()
{
// Date类实例化出对象d1和d2
Date d1;
Date d2;
// d1.Init(&d1, 2024, 3, 31);
d1.Init(2024, 3, 31);
d1.Print();
d2.Init(2024, 7, 5);
d2.Print();
return 0;
}
易错辨析
#include<iostream>
using namespace std;
class A
{
public:
void Print()
{
cout << "A::Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
观察上面这段代码,这段代码有一个易错点,现在p是nullptr,那么按照常理来说,再对空指针进行解引用,那么空指针解引用自动会编译错误,可是事实却并非如此,下面笔者带大家一起分析:
首先我们要知道成员函数的代码在代码段中,如果读者看一下他的反汇编,不难发现,指令是:A::Print(p),因此这个函数其实没有用到this指针,也就是说没有访问任何的成员变量or成员函数,因此不会编译错误。
#include<iostream>
using namespace std;
class A
{
public:
void Print()
{
cout << "A::Print()" << endl;
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
再观察这段代码,这里的Print函数就调用了_a这个成员变量,因此会用this指针解引用,空指针则很难正进行了解引用,就会报错了。
结语
本文就此就结束了,下面笔者将会更新更多内容,请持续关注。