类与对象
一、面向过程和面向对象的初步认识
-
面向过程的语言:C
关注的是过程,通过函数调用逐步解决问题 -
面向对象的语言:C++/Java/C#…
关注的是对象,对象与对象之间的交互 -
面向对象的三大特性:封装,继承,多态。
在类和对象阶段,主要研究类的封装特性
二、类的简单介绍
1. 引入
C++中的结构体区别于C语言
C语言结构体:只能定义变量
C++结构体:可以定义变量也可以定义函数
eg:
#include <iostream>
using namespace std;
typedef int DataType;
struct Stack
{
//缺省参数
void Init(size_t capacity = 4)
{
_a = (DataType*)malloc(sizeof(DataType) * capacity);
if (nullptr == _a)
{
perror("malloc fail");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(const DataType& data)
{
//增容
_a[_size] = data;
_size++;
}
DataType Top()
{
return _a[_size - 1];
}
void Destroy()
{
if (_a)
{
free(_a);
_a = nullptr;
_capacity = 0;
_size = 0;
}
}
//类域是一个整体,所以成员变量在前在后都可以
DataType* _a;
size_t _capacity;
size_t _size;
};
- 上面的Stack就是一个类,Stack是类的名字。函数和变量都可以在类域(后面讲)中定义,类域是一个整体,所以成员变量在成员函数之前或之后都可以。
- 在C++中更喜欢把struct换成class
2. 类的定义
①语法
class className
{
//类体:由成员变量和成员函数组成
};
②类的两种定义方式
- 声明和定义全部放在类体中
eg1:
//介绍一个人的信息
class Person
{
public: //访问限定符
//显示信息
void showInfo()
{
cout << _name << "-" << _sex << "-" << _age << endl;
}
private: //访问限定符
char* _name;
char* _sex;
int _age;
};
注:成员函数在类中定义,可能被编译器当成内联函数处理(不用inline修饰)
- 声明和定义分离
eg2:建议采用这种定义方式
3. 成员变量命名规则
//不良的命名方式
class Date
{
public: //访问限定符
void Init(int year = 4)
{
year = year; //这个year到底是成员变量还是形参参数
}
private: //访问限定符
int year; //不好的命名方式
};
//正确的命名方式
class Date
{
public:
void Init(int year = 4)
{
_year = year;
}
private:
int _year; //只要能更好区分成员变量和形参就好
};
//or...
class Date
{
public:
void Init(int year = 4)
{
mYear = year;
}
private:
int mYear; //member
};
三、类的访问限定符及封装
1. 访问限定符(public、private、protected)
特性:
访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现为止,如果后面没有访问限定符,作用域到}
即类结束
注:访问限定符只在编译时有用,当数据映射到内存之后,没有任何区别。
2. class和struct的区别
- C++需要兼容C,所以C++中的struct可以当结构体使用。
- C++中的struct也可以像class定义类。区别是struct定义的类默认访问权限是public,class定义的类默认访问权限是private。
3. 封装
- C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善。通过访问权限选择性的将其接口提供给外部的用户使用。
- 封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
- 封装本质是一种管理,让用户更方便使用类。
四、类域
类定义了一个新的作用域,类的所有成员都在类的作用域中,在类体外定义成员时,需要使用 ::
域作用操作符指明成员类域。
eg:
class Person
{
public:
void PrintPersonInfo();
private:
char _name[20];
char _gender[3];
int _age;
};
//这里定义PrintPersonInfo时,要指明其属于Person这个类域
void Person::PrintPersonInfo()
{
cout << _name << "-" << _gender << "-" << _age << endl;
}
注意:只有全局域和局部域,影响生命周期
五、对象
1. 类的实例化
类的实例化:用类的类型创建对象的过程
- 类是对 对象进行描述的,限定了类的成员,并没有分配实际的内存空间进行存储。就如房子的设计图,但并不是真正的房子。
- 一个类可以实例化很多对象,实例化的对象占用实际的物理空间,存储类成员变量。
eg1:以上面类域实现的Person类为例
int main()
{
//Person._age = 100; //不能这样使用,因为没有实例化,Person类没有实际空间
Person per1; //Person类实例化的一个对象
Person per2; //Person类实例化的另一个对象
per1._age = 100; //只有实例画的对象才有空间存储类的成员变量
}
注:在main中访问类的成员变量,需要其在类中的访问限定符是公有。类域中定义的是私有,但是我们知道这点就可以了
图解:类中不能存数据只有实例化出的对象才能实际存储数据占用物理空间。
2. 对象大小的计算
类对象的大小:只算成员变量,不算成员函数。
成员函数的存储方式可能是下面两种:因为无法实验,只能根据编译器情况
- 代码保存一份,在对象中保存存放代码的地址。
- 成员函数放在公共代码段。
eg1:类中有成员变量和成员函数
class A1
{
public:
void PrintA1()
{
cout << _a << endl;
}
private:
char _a;
int b;
};
int main()
{
cout << sizeof(A1) << endl;
return 0;
}
//代码运行结果:8
eg2:类中仅有成员函数
class A2
{
public:
void f2() {}
};
int main()
{
cout << sizeof(A2) << endl;
return 0;
}
//代码运行结果:1
eg3:类中什么都没有 — 空类
class A3
{};
int main()
{
cout << sizeof(A3) << endl;
return 0;
}
代码运行结果:1
小结:
一个类的大小,实际就是该类中成员变量之和,要注意结构体内存对齐。注意空类的大小,空类比较特殊,编译器给空类一个字节来唯一标识这个类的对象。
注:如果对结构体对齐有疑惑可以看一下这篇博客结构体
六、this指针
1. 引出this
eg:定义一个日期类
class Date
{
public:
void Init(int year = 1991, int month = 4, int day = 25)
{
_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(2022, 1, 11);
d2.Init(2022, 1, 12);
d1.Print();
d2.Print();
return 0;
}
这里出现一个问题,对象都是调用一个类中的一个同名函数,函数如何区分不同对象?
- C++通过设置this指针解决该问题。
- C++编译器给每个“非静态的成员函数”增加了一个隐藏的指针参数,让指针指向当前对象(函数运行时调用该函数的对象)。在函数体中所有“成员变量”的操作,都是通过指针去访问。
图解:下图中实际this指针的类型是Date* const this
2. this指针的特性
- this指针的类型:类的类型* const 。this指针不能改变。
- this指针不能用于形参和实参的显示传递,但是可以在函数内部使用。
- this指针本质是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。
- this指针是“成员函数”第一个隐含的指针形参,,一般由编译器通过ecx寄存器自动传递,不需要我们传递。
3. this指针问题
-
this指针存在哪里?
this指针存在在对象里。对象以new的方式实列化就在堆区,以static的方式实例化就在全局数据区,正常实例化就在栈区。 -
this指针可以为空吗
eg1:
#include <iostream>
using namespace std;
class A
{
public:
void Print()
{
cout << "Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
eg2:
#include <iostream>
using namespace std;
class A
{
public:
void PrintA()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA();
return 0;
}
图解:
p->PrintA();
的汇编代码