C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成
举个栗子
例如设计一个简单的外卖点餐系统,面向过程关注的是点餐,送餐,接单的过程。
而面向对象关注的是用户,商家,骑手,关注对象之间的关系。
在C语言中结构体只能定义变量,而在C++中,结构体内不仅可以定义变量,还可以定义函数。
typedef int STDataType;
struct stack
{
//定义函数
void StackInit()
{
a = (STDataType*)malloc(sizeof(STDataType));
top = 0;
capacity = 0;
}
void StackPush( STDataType x)
{
//...
}
void StackPop( )
{
//...
}
STDataType StackTop()
{
//...
}
//定义变量
STDataType* a;
int top;
int capacity;
};
int main()
{
stack ST;
ST.StackInit();
ST.StackPush(1);
ST.StackPush(2);
ST.StackPush(3);
ST.StackPush(4);
ST.StackPush(5);
//...
return 0;
}
我们在数据结构阶段学习的栈,用C++实现的话,定义函数和定义变量都可以放在结构体中。
结构体的定义,在C++中更喜欢用class来代替
class className
{
类体:由成员函数和成员变量组成
}; 一定要注意后面的分号
class为定义类的关键字,className为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。
类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。
①声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
②声明和定义分离,声明放在.h文件中,成员函数定义放在.cpp文件中
所以我们一般把规模较小的函数,想成为内联的函数,就直接在类里面定义。
规模较大的函数,声明和定义分离。
<访问限定符说明>
- public修饰的成员在类外可以直接被访问
- protected和private修饰的成员在类外不能直接被访问,只能在类里面访问
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
- 如果后面没有访问限定符,作用域就到 } 即类结束。
- class的默认访问权限为private,struct为public(因为struct要兼容C)
面向对象的三大特性:封装、继承、多态
在类与对象阶段,我们只研究三大特性中的封装
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
举个栗子,对于计算机这个复杂的设备,提供给用户的就只有开关机键、显示器,USB插孔等,但实际上电脑真正工作的是CPU、显卡、内存等一些硬件元件。对于我们用户而言,并不关心内部的核心部件,只需要知道如何开机、如何通过键盘和鼠标与计算机进行交互完成日常工作即可。因此在出厂时,会在计算机外部套上壳子,将内部实现细节隐藏起来。这种隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互的方式就叫做封装
在C++中,我们使用类把数据和方法都封装一下,不想给别人看到的,就用 protected或private封装起来,开放一些共有的成员函数对成员合理访问。
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类外定义成员时,需要使用 :: 操作符指明成员属于哪个类域
用类类型创建对象的过程,称为类的实例化
类是对对象进行描述的,定义出一个类并没有分配实际的物理存储空间。只有进行类的实例化,创建对象,才会分配内存空间,存储成员变量。(一个类可以实例化出多个对象)
举个栗子,用类创建对象就好比用设计图纸建造房子,类就是设计图纸,上面只有如何建造房子,但并没有实体的房子存在,只有用设计图纸建造出房子,才能供人们去居住。类也一样,只有用类创建对象,才能存储数据,占用空间。
类中既有成员函数,又有成员变量,那么一个类的对象中包含了什么?如何计算一个类的大小呢?
猜测一
对象中包含了类的各个成员
这样做的缺陷是,每个对象中的成员变量是不同的,当一个类创建多个对象时,如果调用同一份函数,每个对象中都会保存一份相同的代码,当创建对象较多,会大量浪费空间。
猜测二
相同函数的代码只保存一份,在对象中保存存放代码的地址
猜测三
只保存成员变量,成员函数存放在公共的代码段
对于猜测二和猜测三,都解决了猜测一的缺点,那么计算机到底是按照哪种方式存储类的呢?一起来验证下吧
从A3中可以看出,空类默认给的大小是1字节,如果空类是0,相当于没有分配空间,不符合常理。A2的值也是1,和空类一样,就可以说明计算机是按照第三种方式存储的,成员函数存放在公共代码段,编译链接的时候根据函数名去公共代码区找函数的地址,对象中只保存成员变量,因为A2中无成员变量,所以值也是0。再去A1验证,按照内存对齐原则,8个字节刚好是两个int的大小。
结论:一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐。
结构体内存对齐规则
1.第一个成员在与结构体偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的对齐数为8
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
class Date {
public:
void InitDate(int year, int month, int day)
{
_day = day;
_month = month;
_year = year;
}
void print()
{
cout << _year << "."<<_month<<"." << _day << endl;
}
protected:
int _day;
int _month;
int _year;
};
int main()
{
Date d1;
Date d2;
d1.InitDate(2022, 8, 21);
d2.InitDate(2022, 8, 23);
d1.print();
d2.print();
return 0;
}
对于上述类,有这样的一个问题:
Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调用 Init 函数时,该函数是如何知道应该初始化d1对象,而不是初始化d2对象呢?
C++中通过引入this指针解决该问题,C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象,在成员函数里面对成员变量的访问,都是通过该指针。只不过所有操作都是编译器自己完成,不需要用户自己传递。
本质
但需注意,实参和形参位置不能显示传递和接受this指针(所以上图会报错)
但可以在成员函数内部使用this指针
this指针的特性
1.this指针的类型:类的类型* const,即成员函数中,不能给this指针赋值。
2.只能在“成员函数”的内部使用
3.this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this指针。所以对象中不存储this指针。
4.this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。
Question1: this指针可以为空吗?
我们来看看下面的两段代码,解答这个问题
下面程序编译运行结果是?
Example1
class A1 {
public:
void Print()
{
cout << this << endl;
cout << "Print()" << endl;
}
protected:
int _a;
};
int main()
{
A1* ptr = nullptr;
ptr->Print();
return 0;
}
我们已经知道,成员函数并没有存储在对象中,所以调用函数并不会访问空指针,只不过空指针作为参数传递给了成员函数,没有对空指针进行解引用。所以程序正常运行。
Example2
class A2 {
public:
void Print()
{
cout << this << endl;
cout << _a<< endl;
}
protected:
int _a;
};
int main()
{
A2* ptr = nullptr;
ptr->Print();
return 0;
}
此时调用成员函数Print需要到指针所指的对象中访问_a,相当于对空指针进行了解引用,程序会崩溃。
综上所述,this指针可以为空,但不能访问空指针。
Question2 :this指针存在哪里?
我们知道this指针是作为成员函数的形参,所以一般情况下会存在栈区,但有些情况下会有优化。
vs下传递this指针是通过ecx寄存器传递的。这样this访问成员变量会提高效率。