1. 类
我们之前提及过C语言是面向过程的语言,其解决问题的方式是关注问题过程,然后逐步解决。而C++是面向对象编程,聚焦于对象,依靠多个对象之间的交互关系解决问题。而类这个概念的引入则是面向对象的最深刻体现。
1.1 C++中的结构体
C++兼容C,所以C++的结构体完全涵盖了C语言中结构体的语法形式。除此之外,C++结构体也做出了一些改进,如定义结构体变量不再一定需要struct关键字,不过最大的变化还是体现在结构体内可以定义函数了。
在结构体内定义的函数可以直接使用结构体中的成员变量,在调用成员函数时可以直接使用成员访问的方式就行调用,函数中的成员变量则会自动被替换为调用结构体变量对应的结构体变量。
struct Stack {
//C++的结构体内既可以定义变量,也可以定义函数
//变量
int* _arr;
int _capacity;
int _top;
//函数
void Init(int capacity = 4)
{
_arr = (int*)malloc(sizeof(int) * capacity);
if (_arr == nullptr)
{
perror("malloc fail");
return;
}
_capacity = capacity;
_top = 0;
}
};
int main()
{
//定义结构体变量可以不加struct
struct Stack st1;
Stack st2;
//调用成员函数
st1.Init();
st2.Init(8);
}
需要注意的是成员变量的命名,尽量加入一些标识使得可以很容易分辨出是一个成员变量,以防在成员函数中调用产生分不清的情况。类的成员变量命名也要注意这一点。
1.2 C++的类
但是在C++中我们一般使用类来替代结构体,类的关键字为class。
1.2.1 类的定义
1.2.1.1 声明和定义全部放在类体中
声明和定义同时放在类体中,这种方式比较简便,就像C语言阶段的声明定义不分离一样。
class Date
{
int _year;
int _month;
int _day;
void Show()
{
cout << _year << '-' << _month << '-' << _day << endl;
}
};
1.2.1.2 声明放在类的头文件,定义放在类的实现文件
这种方法类似于C语言中的声明和变量分离,比较规整并且便于合作开发。
这种方法需要注意类的作用域,即需要 类名::成员名 来指明成员所属的类域。
date.h
#pragma once class Date { int _year; int _month; int _day; void Show(); };
date.cpp
#include"date.h" void Date::Show() { cout << _year << '-' << _month << '-' << _day << endl; }
1.2.2 类的访问限定符
1.2.2.1 封装
面向对象有三大特性:封装、继承、多态。
封装指的是将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。在C++中可以借助访问限定符,来控制哪些成员或方法可以在类外部使用,从而实现封装的特性。
1.2.2.2 访问限定符
C++的访问限定符有三个:public(公有),protected(保护),private(私有)。这三个访问限定符只有public修饰的成员支持在类外被访问,而protected和private是不可以的。
访问限定符在C++中的形式是访问限定符带一个冒号。其限定范围是自符号起到下一个访问限定符或类结束为止。
class成员的默认访问权限是private,struct成员的默认访问权限是public。
struct Stack {
int* _arr;
int _capacity;
int _top;
void Init(int capacity = 4)
{
_arr = (int*)malloc(sizeof(int) * capacity);
if (_arr == nullptr)
{
perror("malloc fail");
return;
}
_capacity = capacity;
_top = 0;
}
private:
void Show()
{}
};
class Date
{
int _year;
int _month;
int _day;
public:
void Init(int year = 2000, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Show()
{
cout << _year << '-' << _month << '-' << _day << endl;
}
};
int main()
{
struct Stack st1;
//struct默认访问权限是public
st1.Init();
int a = st1._capacity;
//st1.Show();//error
Date d1;
//class默认访问权限是private
//d1._year = 2024; //error
d1.Init();
d1.Show();
}
1.2.3 类的实例化
类和结构体在一定程度上是相似的。在定义结构体或类的时候只是限定了有哪些成员,类似于给出图纸,但此时并没有在内存中分配空间。直到我们创建结构体变量或类对象,这时候才会在内存中开辟空间,我们才能正常访问使用。这一步就叫做类的实例化。
class Date
{
int _year;
int _month;
int _day;
public:
void Init(int year = 2000, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Show()
{
cout << _year << '-' << _month << '-' << _day << endl;
}
};
int main()
{
//Date.Init(); //error 没有实例化
Date d1;
d1.Init();
}
1.2.4 类的大小
类的大小计算方式与结构体相同。类中的成员函数不会存放在对象开辟的空间中,而是统一放在代码段中。所以类的大小只与成员变量有关而与成员函数无关。当类中没有成员函数时,编译器中会为这个空类对象分配一个字节的空间作为占位。
class C1
{
int a;
char c;
double d;
void func()
{}
};
class C2
{
void func()
{}
};
class C3
{};
int main()
{
cout << sizeof(C1) << endl; //16
cout << sizeof(C2) << endl; //1
cout << sizeof(C3) << endl; //1
}
1.3 this指针
在了解了这么多后有一个问题,在调用成员函数时,我们并没有传递对象的相关信息,函数凭什么知道我们调用的对象是d1而不是d2呢?这是因为this指针发挥了作用。
在C++中,编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数这也就是我们所要介绍的this指针。函数调用时,this指针会指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量” 的操作,都是通过this指针去访问的。只不过所有的操作对用户是透明的,即用户不需要来传递,由编译器自动完成。
我们可以通过观察汇编代码发现这个隐藏的this指针传参。
class Date
{
int _year;
int _month;
int _day;
public:
//void Init(Date* const this, int year = 2000, int month = 1, int day = 1)
void Init(int year = 2000, int month = 1, int day = 1)
{
this->_year = year;
_month = month;
_day = day;
}
//void Show(Date* const this)
void Show()
{
cout << _year << '-' << _month << '-' << _day << endl;
}
};
int main()
{
Date d1;
d1.Init(2024, 2, 25);
d1.Show();
}
我们对this指针进行总结:
①this指针的类型是 类类型* const,这说明在函数成员内不可以修改this,但是修改通过this解引用的值。
②this指针只能在成员函数内使用。
③对象中并不储存this指针,在成员函数调用时将对象的地址作为实参传递给成员函数的this形参。
④this指针真实存在,但是传参是由编译器自动完成的,我们不可以在实参和形参位置显式写出this指针的传递。
⑤我们可以在成员函数内部使用this指针。this作为参数,存储在栈中。
class C
{
public:
int _a;
void func()
{
cout << "func()" << endl; //没有使用this指针,所以没有空指针解引用,不报错
//cout << _a << endl; //error 实际上是this->_a,this是空指针,所以报错
}
};
int main()
{
C c1;
C* pc = nullptr;
pc->func(); //-> 不解引用,而是将pc作为参数传给this,this接收参数,值为nullptr
(*pc).func(); //. 不解引用,也是将pc传给this指针,this接收参数,值为nullptr
}
在成员函数调用时,->或.两个操作符并不进行解引用,而是表示将传给this指针的对象是谁,所以在主函数中并未报错,在成员函数中访问成员变量时才会对this解引用。