【C++初阶】类和对象【上】

文章目录

    • 一、认识类和对象
    • 二、类的引入
    • 三、类的定义和访问限定符
    • 四、类的作用域和实例化
    • 五、类的大小
    • 六、this指针

一、认识类和对象

【C语言】【面向过程
【C++初阶】类和对象【上】_第1张图片
【C++】【面向对象
C++是面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
【C++初阶】类和对象【上】_第2张图片

二、类的引入

在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语言是从上到下搜索,所以定义的顺序会有限制。
【类的访问限定符】:在类中,存在访问限定符
【C++初阶】类和对象【上】_第3张图片

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++中的实例化对象,我们想要存储实际的数据,我们是要占用物理空间的。
【C++初阶】类和对象【上】_第4张图片
声明和定义分离】声明和定义分离要体现类域,那么如何体现呢?

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,这个时候,域作用限定符的作用就体现出来了。-=

五、类的大小

【问】一个类中,既有成员变量也有成员函数,那么一个类的对象中包含了什么,如何计算类的大小呢?

要回答上面这个问题,我们不妨先来猜测一下,类对象在内存中是如何存储的
【C++初阶】类和对象【上】_第5张图片
第一种就是,在这个类对象中,包含了类中的,不管是成员变量还是成员函数的所有成员,这样我们想到一个内存问题,如果创建了很多个类的对象,但是每个类的对象中全部都包含了同一个成员函数,一定会有很多很多的冗余,相同代码保存多次,也会占用很多内存空间,造成空间的浪费。

【C++初阶】类和对象【上】_第6张图片
这种存储的方法就是,把类成员函数表的地址保存在类的对象中,这样每一个指针只会占用4/8个字节,然后只会在类成员函数表中保存一份成员函数,这样就大大减少了空间的浪费,只是需要在调用成员函数的时候,到类成员函数表中,去寻找一下成员函数。

【C++初阶】类和对象【上】_第7张图片
这种方法就是把所有的成员函数都放在公共代码区,如果需要调用时候,就全部到公共代码区去找,这样就不用在类的对象中存储一个类成员函数表的地址,而是统一到公共代码区寻找成员函数。

回答问题
如果这样让选择的话,会选择哪一种存储方式呢?
第一种肯定是被排除的,第一种就像在每栋房子里面都放置一份健身器材,是没有必要的,但在第二种和第三种存储方式中,我们选择哪一种会更好呢?
第二种就像是在小区的某一个地方有一个健身房,每一家都给一个健身房的钥匙,并告诉路线。
第三种就是,在小区里面有一个健身房,就在那个位置,也不用给小区居民配备钥匙了,有需要的话就直接到健身房去。
很多人都会在这里抉择不定,有人会觉得第二种好,还有的会觉得第三种好,大家都知道健身房在哪里,如果还给大家钥匙,还要告诉大家路线,是会存在浪费的。
那实际的存储是什么样的呢? 是第三种 。
为什么选择3呢? 因为没有必要,所有的函数都放在函数表中去找,不如直接放在公共的代码区。

计算类的大小】弄明白了上面的类里面的成员处于什么样的状态,我们很容易就能知道,类的大小是如何计算的。
跟结构体中的计算结果是一样的,也是存在内存对齐问题的,下面附上结构体的计算方法。
【C++初阶】类和对象【上】_第8张图片
如果是空类的话,注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。【空类的大小是一个字节】
空类的一个字节是为了标识对象存在,是为了占位。


六、this指针

【隐含的this指针】
在类中,每个成员函数的第一个参数是隐藏的this指针
Date日期类

#include
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++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。定义和传递都是编译器的活,我们不能去抢,即传递的时候我们不能传递,但是我们可以使用。

【C++初阶】类和对象【上】_第9张图片
this指针使用】红框部分,传递和参数部分都是不需要我们给出的

【this指针特性】

  1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
  2. 只能在“成员函数”的内部使用
  3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给
    this形参。所以对象中不存储this指针。
  4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传
    递,不需要用户传递
    【C++初阶】类和对象【上】_第10张图片

【面试题】
1、this指针存在哪里?
【C++初阶】类和对象【上】_第11张图片

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指针的出现,是让编译器多干一些事情,我们少干一些事情,大大提高了我们的效率。

你可能感兴趣的:(C++,c++)