C++类和对象(上)

0 阅读5分钟
前言

从本文开始,笔者将开始分享自己学习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指针解引用,空指针则很难正进行了解引用,就会报错了。

结语

本文就此就结束了,下面笔者将会更新更多内容,请持续关注。