005 C++ 类与对象

前言

大家好,本文将会为您带来类与对象(定义,实例化,访问限定符,this指针,构造函数)相关知识

类与对象

类的引入

C语言结构体中只能定义变量,在C++中,结构体不仅可以定义变量,也可以定义函数
struct stack
{
       //成员函数
       void init()
       {
              a = nullptr;
              top = capacity = 0;
       }
       //成员变量
       int* a;
       int top;
       int capacity;
};

类的定义

class  classname
{

//类体:由成员函数和成员变量组成

};
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。 类体中内容称为类的成员,类中的变量称为类的属性或成员变量,类中的函数称为类的方法或者成员函数。

类的两种定义方式:
1、声名与定义分离

Test.h
class Person
{
public:
       //显示基本信息
       void showInfo();
private:
       char _name[20];              //名字
       char _gender[3];      //年级
       int _age;                    //年龄
};

Test.cpp
#include"Test.h"
void Person::showInfo()
{
       cout << _name << " " << _gender << " " << _age << endl;
}

访问限定符

访问限定符限制的是类外用类名访问的成员函数和变量 类定义了一个新的作用域,类的所有成员都在类的作用域中,在类外定义成员时,需要使用类名+:: (作用域操作符)
  1. public修饰的成员在类外可以直接被访问
  2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
  3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
  4. 如果后面没有访问限定符,作用域就到 } 即类结束。
  5. class的默认访问权限为private,struct为public(因为struct要兼容C)
    一般我们用class定义一个类,而访问限定符通常手动添加

类的大小

//类中既有成员函数又有成员变量
class A1 
{
public:
	void f1(){}
private:
	char _ch;
	int _a;
};

//类中仅有成员函数
class A2 {
public:
	void f2()
	{
		;
	}
};

//空类
class A3 
{};

int main()
{
	cout << sizeof(A1) << endl;
	cout << sizeof(A2) << endl;
	cout << sizeof(A3) << endl;
	return 0;
}

打印结果如下:
在这里插入图片描述
为什么类A2,A3的大小相同呢?

原因是:当类为空类时,它的大小为1是因为编译器需要确保每个对象在内存中有独一无二的地址。即使类没有任何成员变量,编译器也会为该类分配一个字节的内存空间,以确保每个对象都有唯一的地址。
注意:这个1字节的大小并不是成员函数的大小,成员函数不存储在每个对象的内存中,而是存在于类的公共代码区中,类的大小取决于其成员变量的大小。因此我们也可以理解A1的大小为什么是8个字节了

类的实例化

用类类型创建对象的过程,称为类的实例化,类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;比如:入学时填写的学生信息表,表格就可以看成是一个类,来描述具体学生信息。
一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量
先看一个日期类的程序

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(2023, 7, 29);
	d1.print();
	d2.print();
	return 0;
}

打印结果
在这里插入图片描述
我们可以看到d1对象我们调用了成员函数完成初始化的操作,这没有什么问题,然而我们没有用Init去完成初始化d2对象,会出现随机值,这是为什么呢?

原因是当我们创建对象(类的实例化)的时候,编译器会自动调用默认的构造函数(如果我们没有提供一个构造函数),但是这个默认的构造函数对内置类型不会做处理(注意:会调用默认构造函数但是不会处理)这也就是为什么会出现随机值。
tips:常见的内置类型包括int、float、Double、bool、char等。

创建对象

//以日期类为例
classname + objects
Date d1 //创建一个d1对象,无参,调用默认的构造函数

引入构造函数

构造函数的主要任务是初始化对象,与我们上述的Init成员函数的工作一样

1、函数名与类名相同
2、无返回值
3、对象实例化时编译器自动调用对应的构造函数
4、构造函数可以重载(写多个构造函数,提供多种初始化方式)
5、如果类中没有显示定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显示定义编译器将不再会生成

class Date
{
	public:
		//无参
		Date()
		{
			cout << "Date()" << endl;
			_year = 1;
			_month = 1;
			_day = 1;
		}
		//半缺省
		Date(int year, int month, int day)
		{
		 cout << "Date(int year, int month, int day)" << endl;
			_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.print();

	Date d2(2023,7,29);
	d2.print();
	return 0;
}

打印结果如下:
在这里插入图片描述
我们可以看到当创建对象的时候,会自动调用构造函数,并且构造函数可以重载,意味着我们可以在创建对象的时候提供多种方式去初始化,但是最推荐的还是用全缺省的构造函数(如下:)

		//全缺省
		Date(int year = 1, int month = 1, int day = 1)
		{
			cout << "Date(int year = 1, int month = 1, int day = 1)" << endl;
			_year = year;
			_month = month;
			_day = day;
		}
		int main()
		{
		Date d3(2023);
		d3.print();
		return 0;
		}
注意:无参的构造函数和全缺省的构造函数都成为默认构造函数,并且默认构造函数只能有一个,无参构造函数,全缺省构造函数、我们没写编译器默认生成的构造函数,都可以是默认构造函数 结合我们之前所讲的缺省参数,这里使用全缺省的构造函数将会更加自由方便。

在这里插入图片描述

引入this指针

经过上述对类的实例化的介绍,不妨提出一个疑问:
Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调用 Init 函
数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?
先看一个程序

class Date
{
       public:
		void Init(int year, int month, int day)
		{
		//this指针可以在类的成员函数中显式使用,在一些场景下是非常有必要的
			cout << this << endl;
			this->_year = year;
			this->_month = month;
			this->_day = 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(2023, 7, 29);
	d2.Init(2023, 7, 28);
	d1.print();
	d2.print();
	return 0;
}
C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。

编译器所做的操作如下:
在这里插入图片描述

this指针的特性:
1、 this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
2、只能在“成员函数”的内部使用
3、 this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给
this形参。所以对象中不存储this指针。
4、this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
接下来我们再看一个程序,让您更好地理解this指针

class A
{
public:
       void Print()
       {
              cout << "print()" << endl;
              //error: cout << _a << endl;  //会变成this->_a
       }
private:
       int _a;
};
int main()
{
       A* p = nullptr;	//创建一个类类型的指针,初始值为nullptr
       p->Print();		
       return 0;
}

我们来看这句代码

 p->Print();

p是一个空指针没有指向有效的对象,然而即使p是空指针,仍然可以调用成员函数,因为编译器会自动将this指针的地址传递给成员函数(而且成员函数的地址位于公共代码区,不存在类里面,因此不需要在类里面找函数,因此这句代码编译器会做优化处理,直接传递this指针,然后再调用公共代码区里的成员函数,没有解引用p去访问成员函数)

cout << "print()" << endl;	   //正常运行
//error: cout << _a << endl;  //会变成this->_a
在print()函数中,使用了_a成员变量,实际上会被编译器转换为 this->a 来访问成员变量,由于p是空指针,访问 this->_a 实际上实在访问一个无效的内存地址,导致了程序崩溃

小结

本文就到这结束啦,如果文本存在疏漏或错误的地方,还请您能够指出,祝您天天开心啦!

posted @ 2023-08-08 09:35  Fan_558  阅读(3)  评论(0编辑  收藏  举报  来源