【C++入门】类和对象Ⅰ:类的定义、访问限定符、实例化、对象的大小、this 指针(含面试题)


一、class – 类

C++ 把 C 语言中的 结构体(struct) 升级成了 类(class),将 数据方法 都封装进类里面,其中的成员会在类域中被全局搜索,没有前后之分。

1.1 类的各个部分介绍

  • class:类的关键字
  • ClassName:类的名字,建议大驼峰书写
  • { }:括号中的是类的主体,放类的成员,注意后面 分号不能省略
  • 类的成员:分为两部分 –>
    • 类中的 变量 称作 类的属性成员变量
    • 类中的 函数 称为 类的方法成员函数
class className
{
	// 类体:成员函数 + 成员变量
}; 

1.2 类定义的两种方式:

  1. 声明和定义都在类体中,在类体中定义函数,会有编译器将其处理成内联函数的风险,一般不推荐这样写。

  2. 声明和定义 分开编写 在 .h 和 .cpp 文件中,推荐这种写法(案例所示)。每个类都是一个域,写函数名时需注意加上作用域操作符 –> 类名::函数名

/*********************** 声明 *********************/
/*                      class.h                  */
class Date
{
	public:
		void Init(int year);
	private:
		int _year;
};
/*********************** 定义 *********************/
/*                    class.cpp                  */
#include"class.h"

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

1.3 访问限定符

C++ 用类将对象的属性和方法 封装 在一块,并通过以下三种 访问限定符 对成员进行修饰从而控制访问权限,选择性的将接口提供给给外部用户使用:

  • public(公有):其修饰的对象在类外可直接被访问
  • protected(保护):不能直接被访问
  • private(私有):不能直接被访问

【使用规则】:

  • 访问权限作用域,从 该访问限定符出现的位置 开始直到 下一个访问限定符出现 时为止,如果后面没有访问限定符,作用域就到 } 即类结束。
  • 如果不设置,class 的默认访问权限为 privatestruct 为 public(因为 C++ 需要兼容 C)。

二、对象(Object)

2.1 对象和类的关系 – 对象是类的实例化

用类创建对象的过程,就是类的实例化。也就是说,对象 是 类实例化 的结果

同一张建筑图纸,可以造出多栋房子。类好比图纸,实例化的对象好比建造出来的房子。

【类可以实例化出 多个对象,实例化出的对象,才占用物理空间。】


2.2 对象的大小

既然对象是被类实例化出来,真实存储的,那么他的大小该如何计算呢?

实际上:
每个对象的 成员变量是不尽相同的,需要独立存储;
而每个对象 调用的成员函数是一样的,只需放到共享公共区域(代码段);

【总结:类的对象中,只存了成员变量,没有存成员函数。类的大小,实际上就是该类中 “成员变量” 之和,注意 内存对齐规则】

/***************************** 类的实例化、对象的大小 ****************************/

class Date    --------------------------------------->{
	public:
		void Init(int year)
		{
		year = _year;
		}
	private:
		int _year;
};

int main()
{
	Date d1;   --------------------------> d1 就是 Date 这个类,实例化出的对象
	Date d2;  ---------------------------> d2 也是 Date 类,实例化出的对象
	d1.Init(2023);
	cout << sizeof(d1) << endl;	// 初始化
	cout << sizeof(d2) << endl;	// 未初始化
	return 0;
}

------
输出结果:
4
4

当类中 只有成员方法 或干脆是个 空类 时,实例化这个类,编译器会给 1byte 的空间,这 1byte 不存放有效数据,只用来占位,标识对象已经被实例化定义出来了

// 只有成员方法
class A1{
publicvoid f1(){}
};
// 空类
class A2{
};
// 调用
int main()
{
	A1 a1;
	A2 a2;
	cout << sizeof(a1) << endl;
	cout << sizeof(a1) << endl;
	return 0;
}

------
输出结果:
1
1

2.3 this 指针

可以借由上述类的实例化案例来思考,d1 和 d2 若都调用 Init() 函数,
传入参数相同的情况下,Init() 函数怎么确定数据该存入 对象d1 还是 对象d2 处呢?

this 指针是一个隐形的形参,只在 类的成员函数 被调用时,编译器才自动处理、转换出来的一条指令
用于 分辨调用对象,即,this 指针是对象的地址

this 指针是调用函数时才产生的,是一个形参,所以存在栈中。(vs 下是通过 ecx 寄存器存的)

/************** 编译器处理后的样子 ****************/

class Date
{
	// ...
		void Init(Date* this,int year)	-----> 函数形参中出现 this 指针
		{
			this->_year = year;
		}
	// ...
};

int main()
{
	Date d1;
	Date d2;
	d1.Init(&d1,2023);	-------------> 调用函数时传入了对象的地址
	d2.Init(&d2,2017);
	return 0;
}

这样的 this 指针 是编译器直接转化成的指令,不需要我们操作。

可以把它想做隐形却存在的参数,实际编写代码时不论函数形参还是实参部分都不能画蛇添足去加写,但是可以在函数里面使用,比如 cout 这个 this 指针会打印出来调用该函数的对象的地址。


举例数据结构,从类的封装,浅浅分析 C 和 C++

都是语言,在底层(空间大小、调用函数...)上没有区别,只是理念不同

【C】:

  1. 数据和方法是分离的
  2. 数据访问的控制是自由的,不受限制的(例1),太随意了

【C++】:

  1. 数据和方法都封装到类里面
  2. 控制了访问方式,愿意让用户访问的 public, 不愿意的就 private(例2)
// 例1:取栈顶元素,接口的设置是为了安全访问,因为初始化 top=0或者1 是底层结构,后面使用的人如果直接访问很容易出问题
//	    推荐使用 StackTop() 接口,而不推荐直接使用,因为 1.不安全  2.需要了解底层结构
//      but!!推荐不是强制,还是会有人“闯红灯”
int mian()
{
	ST st;
	StackInit(&st);
	StackPush(&st, 1);
	//..
	int top = StackTop(&st);
	int Top = st.a[st.top - 1]; // 首先不说需要程序员知道,是否需要-1,这个行为还很可能导致越界访问

	return 0;
}

// 例2:同 取栈顶元素
int main()
{
	Stack st;
	st.Init();
	st.Push(1);
	//...
	int top = st.Top();	// the only way... 封装控制着,强制消解了在这步出错的可能性
}

C++ 设置 封装 的意义:更好的规范、分类、管理!!


面试题两道

1、this 指针 存在哪里?
	
	(略)
2、关于 空指针:分析其运行情况( 编译报错? 运行崩溃? 正常运行?)

	分析:func() 和 Init() 不储存在对象中,
	   	 ptr-> 意味着指针调用成员函数,编译器用 call 在公共区域(代码段)找,即使是空指针,这部分的调用不会有问题
		 调用上了函数,而 fun() 只是简单的打印,Init() 里面 this 是空指针,还想对数据解引用,越界访问了,所以导致运行崩溃
	所以哦,不是所有 空指针+解引用符号 都有问题,需要具体分析
class Date
{
	// ...
		void func()
		{
			cout << "func()" << endl;
		}
		
		void Init(int year)
		{
			_year = year;
		}
	// ...
};

/************************** 题目 **************************/
	Date* ptr = nullptr;	
	
4.	ptr->func();					// 正常运行

5.	ptr->Init(2022);			// 运行崩溃

6.	(*ptr).func(); 					// 正常运行

接下篇【Ⅱ】默认成员函数篇:构造+析构、拷贝构造+赋值重载、取地址重载

【C++入门】类和对象Ⅱ:默认成员函数、构造函数、析构函数、拷贝构造函数、赋值运算符重载函数、取地址及const取地址操作符重载(含例题:时间计算器)