类和对象(上)
1.面向过程和面向对象的初步认识
C语言是面向过程的,分析出求解问题的步骤,通过函数调用逐步解决问题。
C++是面向对象的,将一件事请拆分成不同的对象,靠对象之间的交互完成。
举个例子:
(设计一个外卖系统)
C语言面向过程:关注->点餐,配餐,送餐
C++ 面向对象 : 关注->商家,骑手,用户
2.类的引入
C语言中,结构体中只能定义变量(变量的集合)。
C++中,首先C++是兼容了C语言结构体的用法,并且在此基础上把struct升级成了类,结构体中除了变量还可以有函数。
struct Student
{
void PrintStudentInfo()
{
cout << _name << "" << _gender << "" << _age << endl;
}
char _name[20];
char _gender[3];
int age;
};
int main()
{
struct Student s;
return 0;
}
3.类的定义
class className
{
//类题:由成员函数和成员变量组成
};//要注意这里有分号
class是定义类的关键字;classname是类的名字;{} 中 是类的主体;另外注意类定义结束后有分号。
类中的元素称为类的成员;
类中的数据称为类的属性或者成员变量;
类中的函数成为类的方法或成员函数
类的两种定义方式
- 声明和定义全部放在类中,需要注意的是,成员函数如果在类中定义,编译器可能将其当成内联函数处理
class Person
{
public:
void showInfo()
{
cout << _name << "" << _sex << "" << _age << endl;
}
public:
char* _name;
char* _sex;
int _age;
};
- 声明在 .h 文件中,定义在 .cpp 文件中
//.h
class Person
{
public:
void showInfo();
public:
char* _name;
char* _sex;
int _age;
};
//.c
#include"person.h"
void Person::showInfo()
{
cout << _name << "" << _sex << "" << _age << endl;
}
4.类的访问限定符及封装
4.1 访问限定符
C++ 实现封装的方式:用类将对象的属性与方法结合在一起,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
(访问限定符是在类外面访问时要看,再类里面访问时不受限制)
访问限定符说明
1.public修饰的成员在类为可以直接被访问
2.protected和private修饰的成员在类外不能被直接访问
3.访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现为止
4.class的默认访问权限位private,struct的默认访问权限为public(因为struct要兼容C)
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何限定符上的区别。
class A
{
public:
void Init(int a1, int a2)
{
_a1 = a1;
_a2 = a2;
}
private:
int _a1;
int _a2;
};
int main()
{
A aa;
aa.Init(1, 2);//可以访问
// aa._a1就不能访问了
return 0;
}
如果访问不到却想要_a1 _a2怎么办呢?
class A
{
public:
void Init(int a1, int a2)
{
_a1 = a1;
_a2 = a2;
}
void Print()
{
cout << _a1 << endl;
cout << _a2 << endl;
}
private:
int _a1;
int _a2;
};
int main()
{
A aa;
aa.Init(1, 2);
aa.Print();
return 0;
}
面试题
C++中的struct和class的区别是什么?
C++需要兼容C语言,所以C++中的struct可以当作结构体去使用。另外C++中的struct还可以用来定义类,和class定义类是一样的,区别是struct的成员默认访问方式是public,class的成员默认访问方式是private。
4.2 封装
面向对象的三大特性:封装、继承、多态。
封装:将数据和操作数据的方法进行结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
封装本质上是一种管理。
举个例子:
文物,如果什么都不管,就会被破坏,所以我们就先建个博物馆把这些文物给封装起来,某些不让其他人看到,但我们并不是让所有文物都不与外人接触,所以开放了售票通道,可以买票,突破封装,在合理的监管下进去参观某些特定的文物。
同理类也是一样的
我们使用类数据和类方法都封装到一起,不想给别人看到的用private/protected把成员封装起来。开放一些共有的成员函数对成员合理的访问。所以封装本质上是一种管理。
5.类的作用域
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类外定义成员,需要使用::作用域解析附指明成员属于哪个类域。
class Person
{
public:
void showInfo();
public:
char* _name;
char* _sex;
int _age;
};
void Person::showInfo()
{
cout << _name << "" << _sex << "" << _age << endl;
}
6.类的实例化
用类类型创建对象的过程,称为类的实例化
- 类只是一个像模型一样的东西,限定了类有哪些成员,定义一个类并没有分配实际的内存空间来存储它
- 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量
- 打个比方:类实例化出对象就像是使用建筑图纸造出了房子,类就是图纸,只设计出要什么东西,但是并没有实际的建筑存在,同样类也只是个设计,实例化出的对象才能实际存储数据,占用物理空间
7.类对象模型
7.1 计算类对象的大小
类对象的大小,只需要计算 成员变量+内存对齐 即可。
A2 A3的大小都是1字节。
空类给一个字节是为了占位,表示实例化出的对象存在,不存储数据
8.this指针
8.1 this指针的引出
首先 先定义一个日期来Date
class Date
{
public:
void Display()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
void SetDate(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1, d2;
d1.SetDate(2021, 3, 14);
d2.SetDate(2021, 3, 15);
d1.Display();
d2.Display();
return 0;
}
Date类中有SetDate和Display两个成员函数,函数体中没有关于不同对象的区分。当d1调用SetDate函数的适合,函数是如何知道应该设置对象d1而不是d2呢?
C++通过引入this指针来解决这个问题:C++编译器给每个“非静态成员函数”增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
隐含的this指针↑↑↑
打印this,会得到两个地址,一个是d1的地址一个是d2的
8.2 this指针的特性
- this指针的类型:类类型* const
- 只能在成员函数的内部使用
下面这个代码就是错误示范
//这样就是不行的
void Init(Date* this,int year, int month, int day)
{
_year = year;
_motn = month;
_day = day;
}
this是隐含的,我们不能在实参和形参的位置手动去加。
但是在函数成员内部可以使用this指针,如下列代码
(可加可不加)
void Init(int year, int month, int day)
{
this->_year = year;
this->_motn = month;
this->_day = day;
}
- this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。(this指针一定占据形参中的第一个位置)
- this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
面试题
1.this指针存在哪里
先思考这么一个问题,下面这个代码中
p在哪?*p在那?
int main()
{
int* p = malloc(16);
}
首先要明白内存中的大概布局
p是一个局部变量,所以不管它是指针还是普通的变量他都在栈上。
*p(p指向的内容)malloc开了一块空间,所以他在堆上。
*p在堆开了一块空间,然后把地址给了p
回过头来看this,他一定不是存在对象里的!!算对象大小的时候就没有算this指针,比如空类的大小是1.
this指针是一个隐含的形参,所以他在栈中
(特别说明:vs下为了提高效率,this存在ecx寄存器)
2.this指针可以为空吗?
下面的程序会崩溃吗?在哪里崩溃?
class A
{
public:
void PrintA()
{
cout << _a << endl;
}
void Show()
{
cout << "Show()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA();
p->Show();
return 0;
}
主要就是这两块哪个会崩
先说结论:左面的程序会崩,右面的不会
(首先,p是空指针,但是它调用函数并不会崩溃,因为成员函数没有存在对象中,而是存在了公共的代码段,这里调用的时候,并没有对p进行解引用。因为这个p只是告诉他PrintA和Show要去p所在A域中去找,另一个是因为p的主要作用是传参,传给this,所以这里是不会崩溃的)
首先系统会把代码处理成
把p传给两个this指针
上面代码PrintA中的this就成为了空指针,并且使用了它,所以程序会崩溃。
而Show中的this虽说也是空指针,但是它并没有用,所以没问题