1、面向过程与面向对象
面向过程 | 面向对象 |
点餐下单 | 用户 |
商家接单 | 商家 |
分配骑手 | 骑手 |
骑手送餐 |
2、类
C++中的类(也可称为结构体)和C语言中的结构体类似,继承了结构体内存对齐,便于访问的特性,但是在C语言中结构体只能定义变量,而类中可以定义函数,常用class表示类的关键字。
/*******一般将类的声明放在.h文件中,类中函数定义放在.cpp文件,
********因为成员函数直接在类中定义,编译器会默认当作内联函数*/
class Stack {
//访问限定符的作用域:从当前访问限定符开始直到遇见下一个访问限定符或者'}'
public://公共代码区间(struct的默认访问权限),成员在类外可以直接被访问
void Init(int capacity);//类中声明的函数称为类的方法或成员函数
void Destroy(); //(.cpp中定义时void Stack:: Destroy())
private://私有区间(class的默认访问权限),只有类的内部成员才能访问
int* _arr; //类中声明的函数称为类的属性或成员变量
int _capacity;//类中声明的变量一般前面加上"_"表示区分
int _top;//类中也可以直接给变量赋缺省值
};
2.1 类对象的存储
一般将成员函数放在公共代码区间段,相当于全局函数的声明(存在静态变量区间),在用sizeof计算类的空间大小时并不会计算进去,因此类的大小计算的是类中成员变量所占字节的个数。而成员变量则根据定义方式来决定其存储区间。当一个类为空类或者只有成员函数时,则此类只会占用一个字节来表示对象的存在,并不会在类中存储数据。
2.2 this指针
this*是一个隐藏在类成员函数中以形参的形式存在,当对象调用成员函数时,将对象地址作为实参传递给this形参,并且一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。
class Date {
public:
//this隐藏在函数形参中,不能在函数中显示,并且this指针是常量形式不能赋值
void Init(int year, int month, int day) {
this->_year = year;
this->_month = month;
this->_day = day;
}
void Print() {
cout<< _year<<"/"<< _month <<"/"<<_day << endl;
}
private:
int _year;
int _month;
int _day;
};
3、构造函数
构造函数的名字与类名相同(不需要void或者其他修饰词),创建类类型对象时由编译器自动调用,且在对象整个生命周期内只是用一次,用来初始化对象。
class Date {
public:
Date(){//不带参数形式,两个Date构成函数重载
_year = 1;
_month = 1;
_day = 1;
}
Date(int year,int month=7,int day=30){//带缺省参数
_year = year;
_month = month;
_day = day;
}
void Print() {
cout<< _year<<"/"<< _month <<"/"<<_day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main() {
Date d1(2024);
Date d2; //不能写成Date d2();编译器会认为这是函数声明,导致报错
d1.Print();
d2.Print();
return 0;
}
另外,当程序员没有写构造函数时,编译器会生成默认构造函数(不带参数形式),默认构造不会初始化内置类型变量(int\char,int*....,这一类型的成员变量可以在类中声明时可以给默认值,作用和缺省参数类似),只会对自定义类型成员起作用。因此一般情况需要程序员自己写构造函数,除非成员变量全是自定义类型。
4、析构函数(定义形式:~+类名)
析构函数是一种无参数无返回值和类同名的特殊函数,其作用并不是完成对对象本身的销毁,对象销毁是编译器完成的,而对象在进行销毁时会自动调用析构函数,完成对象中资源的清理工作。
class Date {
public:
~Date() {
cout << "~Date()" << endl;//通过打印窗口观察这段代码执行否
}
};
int main() {
Date d1;
return 0;
}
另外,和构造函数一样,但是析构函数不能重载。当程序员没有写构造函数时,编译器会生成默认析构函数,如果类中没有资源申请(malloc,realloc申请的空间)时可以直接使用编译器生成的默认析构函数;有资源申请时,一定要写析构函数,否则会造成内存泄漏,因为默认析构函数不会主动释放堆中申请的空间。
5、拷贝构造函数
拷贝函数的作用是将创建的对象进行拷贝,拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
class Date {
public:
Date() {
_year = 1;
_month = 1;
_day = 1;
}
//如果函数写成Date(Date d),这种传值构造拷贝称为浅拷贝,调用Date d2(d1);时
//目的是将d1中日期拷贝到d2中,调用函数后先要将d1传给形参d,又涉及到对象的拷贝,
//因此又会调用拷贝构造函数,依次循环导致,程序无限递归。
Date(Date& d) {
cout << "Date(Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main() {
Date d1;
Date d2(d1);//或者写成Date d2 = d1;
return 0;
}
另外,若没有定义拷贝构造函数,编译器会生成默认拷贝构造函数,默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。即对于上面所写的Date传值代码中即使没有定义拷贝构造,编译器也会默认生成,对象中的内置类型就按照字节方式直接拷贝。但是对于一些类中涉及资源申请时,比如栈,如果直接用编译器生成的拷贝构造函数,而默认构造函数是按照值拷贝的,当程序退出要销毁对象时,就会对同一空间调用两次析构函数,也就是释放两次空间,导致程序报错。因此这种情况要程序员自己定义拷贝构造函数。
class Stack {
public:
Stack(size_t capacity = 4) {
_arr = (int*)malloc(sizeof(int)* capacity);
if (_arr == nullptr) {
perror("malloc fail");
exit(1);
}
_top = 0;
_capacity = capacity;
}
Stack(const Stack& s) {//深拷贝
_arr = (int*)malloc(sizeof(int) * s._capacity);
if (_arr == nullptr) {
perror("malloc fail");
exit(1);
}
memcpy(_arr,s._arr,sizeof(int)*s._top);
}
private:
int* _arr;
int _capacity;
int _top;
};