面向过程和面向对象初步认识
C语言是面向过程的语言,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题
以泡一杯咖啡为例🤪
将水壶接满水->将水加热->向杯子中加入适量的咖啡粉->将热水倒入杯子中
而C++是基于面向对象,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成
还是上面的问题,但是思路不同
总共有四个对象:
水壶:属性有容量和加热温度,行为包括加热
杯子:属性有当前液体体积和容量
咖啡粉:具有属性如种类和重量
人:能够执行操作水壶、冲泡咖啡等动作
类的引入
C语言结构体中只能定义变量,在C++中,结构体不仅可以定义变量,也可以定义函数
#include <iostream>
using namespace std;
typedef int DataType;
struct Stack
{
// 定义函数
void Init(size_t capacity) {}
void Push(DataType data) {}
DataType Top() {}
void Destory() {}
// 定义变量
DataType* _array;
size_t _capacity;
size_t _size;
};
int main()
{
Stack s;
s.Init(10);
s.Push(1);
s.Push(2);
s.Push(3);
cout << s.Top() << endl;
s.Destory();
return 0;
}
在上述Stack
的定义中,c++更喜欢用class来定义
类的定义
与结构体定义类似,类的定义如下所示
class className
{
// 由成员变量和成员函数组成
}; // 注意分号不能省略
class
为定义类的关键字,className
为类的名称,{}中为类的主体
在{}中的内容也称类的成员:类中的变量称为类的属性或成员变量;类中的函数称为类的方法或者成员函数
与在C语言中定义函数类似,类也有两种定义方式:
- 将声明和定义全部放在类的主体中,成员函数如果在类中定义,编译器可能会将其当成内联函数来处理;
class Person
{
void ShowInfo()
{
cout << _name << "-" << _age << endl;
}
char* _name;
int _age;
};
- 将声明放在头文件中,定义放在源文件中,在定义中成员函数前需加类名::
// 头文件.h
class Person
{
void ShowInfo();
char* _name;
int _age;
};
// 源文件.cpp
void Person::ShowInfo() // 需要加域作用限定符
{
cout << _name << "-" << _age << endl;
}
一般情况下,会采用第二种方式
类的访问限定符和封装
访问限定符
c++中用类将对象的属性和方法结合在一起,通过访问权限选择性的将其接口提供给外部用户使用
public (公有),修饰的成员在类外可以直接被访问
protected (保护),修饰的成员在类外不可被访问
private (私有),修饰的成员在类外不可被访问
上述范围限定符的作用域从该访问限定符出现的位置开始知道下一个访问限定符出现为止,如果后面没有访问限定符,则作用域到}就结束
class
默认访问权限为private
,struct
为public
(struct
兼容C语言)
封装
面向对象的三大特性:封装、继承、多态
封装:将数据和方法结合起来,隐藏对象的属性和实现细节,仅公开接口来和对象进行交互
以一个笔记本电脑为例,厂商会把CPU、显卡、内存等硬件组装起来,只开放键盘、屏幕、USB插孔、开关机键提供给用户使用,而用户不需要关心CPU、显卡是如何工作
类似的,我们在定义类时可以控制哪些数据或方法在类外部可以被访问
typedef int DataType;
class Stack
{
public:
// 定义函数
void Init(size_t capacity) {}
void Push(DataType data) {}
DataType Top() {}
void Destory() {}
private:
// 定义变量
DataType* _array;
size_t _capacity;
size_t _size;
};
类的实例化
在现实生活中,会依照图纸盖房子,图纸本身不占据土地,但是用图纸盖的房子会占据土地
与结构体和盖房子似,类的定义(类比为图纸)本身不分配内存来存储,但是实例化的对象,会占用实际的物理空间(由图纸盖的一栋栋房子)
类的大小和存储方式
类的存储方式会影响类的大小,类的存储方式可能有以下三种:
- 对象中包含类的成员变量和成员函数;
- 对象中包含类的成员变量和成员函数的地址;
- 对象中只包含类的成员变量,而成员函数保存在公共的代码段;(实际存储方式)
// 类中既有成员变量,又有成员函数
class A1
{
public:
void f1() {}
private:
int _a;
};
// 类中仅有成员函数
class A2
{
public:
void f2() {}
};
//类中什么都没有
class A3
{
};
int main()
{
A1 a1;
A2 a2;
A3 a3;
cout << sizeof(a1) << endl; // 大小为4
cout << sizeof(a2) << endl; // 大小为1
cout << sizeof(a3) << endl; // 大小为1
return 0;
}
运行上述代码可得,一个类的大小,实际是该类中“成员变量”之和,也会遵循内存对齐
空类的大小为1
this指针
#include <iostream>
using namespace std;
class Date
{
public:
void Init(int year = 1970, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1, d2;
d1.Init(2021, 12, 31);
d2.Init(2022, 1, 1);
d1.Print();
d2.Print();
return 0;
}
对于上述程序,Date
类中有Init
与Print
两个成员函数,函数体中没有关于不同对象的区分,那当d1调用Init函数时,Init
是如何知道初始化d1还是d2呢?
对于这个问题,c++中引入了this
指针,c++编译器给每个“非静态的成员函数”增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有关于成员变量的操作,都是通过该指针去操作,只不过所有操作用户都看不到,即不需要用户来传递,编译器自动完成
特性
this
指针的类型是类的类型 * const,在成员函数中,不能给this
赋值- 只能在成员函数内部使用
this
指针本质上是成员函数的形参,当对象调用成员函数时,将对象地址作为实参传递给this
形参,所以对象不存储this
指针this
指针是成员函数第一个隐含的指针形参,一般情况下编译器通过ecx寄存器自动传递,不需要用户传递
来看一个程序
class A
{
public:
void Print()
{
cout << _a << endl;
}
private:
int _a;
};
class B
{
public:
void Print()
{
cout << "Print()" << endl;
}
private:
int _b;
};
int main()
{
// 会报错
A* pa = nullptr;
pa->Print();
// 不会报错,可以正常运行
B* pb = nullptr;
pb->Print();
return 0;
}
首先看pa->Print()
,pa
是空指针,但实际上Print()
是放在公共代码段,此操作不会解引用,因此可以正常运行,之后会把pa传递给Print()
,在Print()
函数内部,语句cout << _a << endl;
实际是cout << this->_a << endl;
,但是this = nullptr
,空指针解引用会引发程序崩溃
再看pb->Print()
,同样的,调用Print()
不会报错,而Print()
内部,没有涉及空指针,所以程序会正常运行