一、认识类和对象
【C语言】【面向过程】
【C++】【面向对象】
C++是面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
二、类的引入
在C语言实现数据结构的时候,我们经常使用到struct定义一个结构体,但是在结构体中,只有成员变量,却没有成员函数,类中就是包括了属性和方法,既有struct中的成员函数,也有在struct外实现的函数体,我们一般用class来标识类,在后面我们会介绍到,在C++中,strcut默认的访问限定符是public(公有),class就是strcut的升级,class中有strcut的所有功能,就是兼容于strcut,而class的默认访问限定符则是private(私有)。
如果是在C++编译器中,使用struct,也可以在struct中定义函数,编译器会默认把它当作类来处理。
三、类的定义和访问限定符
class ClassName
{
//类体---由成员变量和成员函数组成
//访问限定符
public:
//成员函数
void Print()
{
}
private:
//成员变量
int capacity;
};
class为类的关键字,ClassName为类名 {}中的是类的主体,和结构体一样,{}后面的分号是不能省略的。
在类中,成员函数和成员变量可以定义在任意位置,是没有限制的,因为在搜索的时候,是在整个类中搜索,对比与C语言,C语言是从上到下搜索,所以定义的顺序会有限制。
【类的访问限定符】:在类中,存在访问限定符
1、public修饰的成员在类外可以直接访问
2、protected和private在类外不可以直接访问(两者是类似的)
3、访问限定符的作用范围是:从这一次出现的时候开始,到下一次出现访问限定符的时候结束
4、(前面提到)class为public,struct默认为private
【面试题】我们都知道C++的三大特性是封装,继承和多态,那什么是封装呢?
解答:
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来
和对象进行交互。
封装本质上是一种管理,让用户更方便使用类。
在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来
隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。
【面试题】class和struct的区别是什么?
解答:C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来
定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的类
默认访问权限是private。注意:在继承和模板参数列表位置,struct和class也有区别,后序给大
家介绍。
四、类的作用域和实例化
【类的作用域】
在类外进行访问类中的成员的时候,需要用到 : :作用域操作符来指明成员属于哪一个类域。
class Person
{
public:
void PrintPersonInfo();
private:
char _name[20];
char _gender[3];
int _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
cout << _name << " "<< _gender << " " << _age << endl;
【类的实例化】
我们在定义类的时候,只是对于类的声明,并没有真正地开辟空间,我们想象,定义类的时候,就像是盖房子的图纸一样,并没有实际意义上的真正的房子出现,而类的实例化就是把图纸上的内容给变成房子,让其有真正的实物产生,而反看C++中的实例化对象,我们想要存储实际的数据,我们是要占用物理空间的。
【声明和定义分离】声明和定义分离要体现类域,那么如何体现呢?
class Stack
{
public:
void Init(int N = 4);
void Push(int x);
private:
int* a;
int capacity;
int top;
};
class Queue
{
public:
void Init();
void Push(int x);
private:
};
#include"Stack.h"
void Stack::Push(int x)
{
//...
}
void Stack::Init(int N)
{
top = 0;
capacity = 0;
}
void Queue::Init()
{
}
void Queue::Push(int x)
{
//...
}
在之前C++初阶入门的时候,我们有介绍到,命名空间域的概念,在类中,也有一个类域,因为在Stack中有Init,在Queue中也有Init,这个时候,域作用限定符的作用就体现出来了。-=
五、类的大小
【问】一个类中,既有成员变量也有成员函数,那么一个类的对象中包含了什么,如何计算类的大小呢?
要回答上面这个问题,我们不妨先来猜测一下,类对象在内存中是如何存储的
第一种就是,在这个类对象中,包含了类中的,不管是成员变量还是成员函数的所有成员,这样我们想到一个内存问题,如果创建了很多个类的对象,但是每个类的对象中全部都包含了同一个成员函数,一定会有很多很多的冗余,相同代码保存多次,也会占用很多内存空间,造成空间的浪费。
这种存储的方法就是,把类成员函数表的地址保存在类的对象中,这样每一个指针只会占用4/8个字节,然后只会在类成员函数表中保存一份成员函数,这样就大大减少了空间的浪费,只是需要在调用成员函数的时候,到类成员函数表中,去寻找一下成员函数。
这种方法就是把所有的成员函数都放在公共代码区,如果需要调用时候,就全部到公共代码区去找,这样就不用在类的对象中存储一个类成员函数表的地址,而是统一到公共代码区寻找成员函数。
【回答问题】
如果这样让选择的话,会选择哪一种存储方式呢?
第一种肯定是被排除的,第一种就像在每栋房子里面都放置一份健身器材,是没有必要的,但在第二种和第三种存储方式中,我们选择哪一种会更好呢?
第二种就像是在小区的某一个地方有一个健身房,每一家都给一个健身房的钥匙,并告诉路线。
第三种就是,在小区里面有一个健身房,就在那个位置,也不用给小区居民配备钥匙了,有需要的话就直接到健身房去。
很多人都会在这里抉择不定,有人会觉得第二种好,还有的会觉得第三种好,大家都知道健身房在哪里,如果还给大家钥匙🔑,还要告诉大家路线,是会存在浪费的。
那实际的存储是什么样的呢? 是第三种 。
为什么选择3呢? 因为没有必要,所有的函数都放在函数表中去找,不如直接放在公共的代码区。
【计算类的大小】弄明白了上面的类里面的成员处于什么样的状态,我们很容易就能知道,类的大小是如何计算的。
跟结构体中的计算结果是一样的,也是存在内存对齐问题的,下面附上结构体的计算方法。
如果是空类的话,注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。【空类的大小是一个字节】
空类的一个字节是为了标识对象存在,是为了占位。
六、this指针
【隐含的this指针】
在类中,每个成员函数的第一个参数是隐藏的this指针
【Date日期类】
#include<iostream>
using namespace std;
class Date
{
public:
void Init(int year ,int month ,int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Init(2022, 10, 21);
d1.Print();
Date d2;
d2.Init(2022, 10, 22);
d2.Print();
return 0;
}
编译器会在这里加一个隐含的this指针,this是以关键字形式出现的,所以只能是this
//调用的时候
//相当于Print(&d1)
//Print(&d1)
void Print(Date* this)
{
cout << this->_year
<< "-" << this->_month
<< "-" << this->_day
<< endl;
}
【this指针的特点】
C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。定义和传递都是编译器的活,我们不能去抢,即传递的时候我们不能传递,但是我们可以使用。
【this指针使用】红框部分,传递和参数部分都是不需要我们给出的
【this指针特性】
- this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
- 只能在“成员函数”的内部使用
- this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给
this形参。所以对象中不存储this指针。 - this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传
递,不需要用户传递
【面试题】
1、this指针存在哪里?
2、this指针可以为空吗?
答:this 指针作为参数传递时是可以为空的,但是如果成员函数中使用到了 this 指针,那么就会造成对空指针的解引用;
3、下面两段代码的运行结果是什么样的?
// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
public:
void Print()
{
cout << "Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
void PrintA()
{
cout<<_a<<endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA();
return 0;
}
【解析】第一段代码,p虽然是一个空指针,但是在调用Print函数的时候,因为Print函数在公共代码区,并不是在类中寻找的,也就是没有发生this->指针的解引用,所以,并没有对空指针进行解引用。
第二段代码,和第一段代码的前半部分是一样的,但是,在调用Print函数的时候,会使用到this->_a,这就对传过去的为空的this指针进行了解引用,所以会造成程序运行崩溃。
this指针的出现,是让编译器多干一些事情,我们少干一些事情,大大提高了我们的效率。