类和对象(上)

1.面向对象和面向过程的初步认识

面向过程和面向对象是一个什么东东?我们得把这个问题弄清楚。

面向过程:是一种以事件为中心的编程思想,编程的时候把解决问题的步骤分析出来,然后用函数把这些步骤实现,在一步一步的具体步骤中再按顺序调用函数。

简而言之:C语言就是面向过程的语言,我们把要完成的内容,一步一步交给函数完成。

如之前写的三子棋:初始化棋盘,打印棋盘,玩家下棋,判断输赢,电脑下棋,判断输赢。每一步都是一个个函数,把整个程序都交给一个个函数,这就是面向过程。

image-20240330120359604

**面向对象:**我们关注的是对象,把一件事拆分成不同的对象,靠对象之间的交互完成。

三子棋就可以分为:玩家,电脑对象,棋盘对象,判断输赢系统对象。把具体的步骤交给每个对象,而三子棋只用考虑对象之间的交互即可。

image-20240330140843257

而C++就是面向对象的语言,但是C++不是纯面向对象的语言,它是基于面向对象的语言,它是既可以面向对象又可以面向过程的语言,因为C++兼容C语言,像隔壁的兄弟JAVA就是纯面向对象的语言,写一个普通的交换函数,都需要用类才能完成。

2.类的引入

在C语言的结构体中,只能定义变量,但是在C++的结构体中,可以定义变量,也可以定义函数。之前的结构体我们是把函数放在结构体外面实现的,但是现在C++中我们就可以把函数和变量一起写到结构体里面。

typedef int DataType;
struct Stack
{
	void Init(size_t capacity)//函数
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}
	void Push(const DataType& data)
	{
		// 扩容b
		_array[_size] = data;
		++_size;
	}

	DataType* _array;//变量
	size_t _capacity;
	size_t _size;

};

但是在C++中我们习惯把struct换成class来定义,后面会说到。

3.类的定义

类的定义和结构体的定义是大差不大的,把struct换成class即可,class里面多了可以定义函数,还有范围权限(后续说)

class className
{
// 类体:由成员函数和成员变量组成
};  // 一定要注意后面的分号

类体中内容称为类的成员:类中的变量称为类的属性成员变量; 类中的函数称为类的方法或者成员函数

类的两种定义方式:

  1. 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。

    //第一种:声明和定义全部放在类体中
    class people
    {
    	void showinfo()
    	{
    		cout << name << "-" << sex << "-" << age << endl;
    	}
    	char* name;
    	char* sex;
    	int age;
    };
    
    
  2. 类声明放在.h文件中,成员函数定义放在.cpp文件中。

对于栈来说,我们实例化结构体时,需要struct stack st,但是在类中我们只需要stack st,即可创建出对象,显而类是更加方便的。

注意:成员函数名前需要加类名::

image-20240330145612892

工程项目中几乎都是使用第二种形式,但是我们学习为了方便,大多都写成第一种形式。

成员变量命名规则的建议:

// 我们看看这个函数,是不是很僵硬?
class Date
{
     void Init(int year)
     {
        // 这里的year到底是成员变量,还是函数形参?
        year = year;
     }
int year;
};

如果像上述这样写,我们就没法分清楚year到底是什么?

所以我们对于成员变量命名上一般都加一杠,如下所示:

class Date
{
     void Init(int year)
     {
        _year = year;
     }
int _year;
};

4.类的访问限定符及封装

4.1 访问限定符

C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。

image-20240330150802833

访问限定符说明:

public修饰的成员在类外可以直接被访问 。

protectedprivate修饰的成员在类外不能直接被访问(此处protected和private是类似的)。

③class的默认访问权限为private,struct为public(因为struct要兼容C)

注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别。

image-20240330152449971

这里实例化出类的对象,但是访问不了Init函数,这是因为类的默认权限是private,类外是不能访问的,如果要访问,就只能加上public访问限定符。

class Date
{
public:
	void Init(int year)
	{
		_year = year;
	}
private:
	int _year;
};
int main()
{
	Date d1;
	d1.Init(10);
	return 0;
}

4.2 封装

C++的三大特性:封装,继承,多态。

封装实现了类的接口和实现的分离。封装后的类隐藏了它的实现细节,也就是说,类的用户只能使用接口而无法访问实现部分。

在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。

总之:封装是一种合理,严格的管控!

5.类的作用域

类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。

class Person
{
public:
 void PrintPersonInfo();
private:
 char _name[20];
 char _gender[3];
 int  _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
   cout << _name << " "<< _gender << " " << _age << endl;
}

6.类的实例化

对类类型创建对象的过程就是类的实例化。

当类还没有创建出对象的时候,它是像一个模型一样,并没有分配实际的内存空间。

image-20240330155508399

类就像是建房子的图纸一样,而实例化出来的对象就像是使用图纸建好的房子一样。

class Date
{
public:
	void Init(int year)
	{
		_year = year;
	}
private:
	int _year;
};
int main()
{
	Date d1;//这样就是类实例化出对象
	return 0;
}

7.类对象模型

7.1 如何计算类对象的大小

问题:类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?如何计算一个类的大小?

比如下面这个代码,类对象的大小是多少呢?

class A
{
public:
    void PrintA()
    {
       cout<<_a<<endl;
    }
private:
   char _a;
   int* _b;
};

7.2 类对象的存储方式

image-20240331091617290

这里可以看出类对象的大小都是8,这里无论你sizeof类还是sizeof实例化出来的对象,其实都是一样的。其实这个8只是对象中成员变量的大小,只包括char和int*,这里使用了内存对齐规则,和C语言的规则是一样的,以前的文章有说:自定义类型之结构体,枚举和联合

image-20240331092755031

如果把类成员变量和类成员函数都存到内存的话。

**缺陷:**每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一个类创建多个对象时,每个对象中都会保存一份代码,相同代码保存多次,浪费空间。那么如何解决呢?

其实只保存成员变量,成员函数存放在公共的代码段。

image-20240331093550172

这里还有一个注意的地方,空类的大小是多少呢?

class A
{
};
int main()
{
	cout << sizeof(A) << endl;
}

image-20240331093952371

是不是感觉好奇怪,空类既没有成员变量也没有成员函数,它的大小应该是0才对啊,但是你有没有想过,如果空类的大小是0,也就是内存中没有空类的地址,那如何找到该空类呢?所以为了解决这种麻烦,内存会为空类开辟一个字节

8.this指针

8.1 this指针的引出

为了好的学习this指针,我们来定义一个日期类Date。

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;
	Date d2;
	d1.Init(2022, 1, 11);
	d2.Init(2024, 1, 12);
	d1.Print();
	d2.Print();
	return 0;
}

image-20240331094930830

可以看出两个对象都分别打印出来了自己的日期。但是有个问题?

Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调用 Init 函 数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?

image-20240331095603098

这里确实是d1和d2的Init函数的地址都是一样的。

这时C++就引入一个this指针来解决这个麻烦。

C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量” 的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。

像上述的代码编译器就会写成这样:

在这里插入图片描述

还有这个成员函数print看似没有参数其实有个隐含的this指针。

void Print(Date*this)
{
	cout << this._year << "-" << this._month << "-" << this._day << endl;
}

这个this指针是编译器加上去的,我们不能显示的写出来。

8.2 this指针的特性

  1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。

    void Print(Date*this)     //其实是void Print(Date* const this)
    //const在*的后面,修饰的是this指针本身,即不能修改this指针
    
    
  2. 只能在“成员函数”的内部使用 。

  3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。

  4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。

这里有两个题试试吧!

// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
     void Print()
     {
         cout << "Print()" << endl;
     }
private:
    int _a;
};
int main()
{
     A* p = nullptr;
     p->Print();
    return 0;
}
// 2.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{ 
public:
    void PrintA() 
   {
        cout<<_a<<endl;
   }
private:
 int _a;
};
int main()
{
    A* p = nullptr;
    p->PrintA();
    return 0;
}
  • 29
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值