【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)。

1.4 class 和 struct

在 C++ 中可以用 struct 关键字定义类,但只有在特殊需求时,才会这样使用。
即,需要将所有成员都对外开放时,我们才会选择使用 struct 去定义类,比如节点的指针。

🌰举个例子

// list 库的源代码,对 list 类的定义如下
template <class T>
struct _list_node{
	typedef void* void_pointer;
	void_pointer next;
	void_pointer prev;
	T data;
};

二、对象(Object)

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

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

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

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


2.2 对象的定义

对象的定义有多种方法,这里只做简单提及,后面都会一一介绍到

  1. 通过 给参数 给对象
    如果定义时 不传参,则不需要加括号,因为如此写法同一个无参数的函数声明一样,编译器无法识别
// 注:class A{}; 的声明过程省略
【构造函数】 进行的初始化:

	A a1;
	//A a1(); --->err..
	A a2(1);
	A a3[10]	// 此处调用了10次构造函数
【拷贝构造函数】 进行的初始化:	

	A a4(a1);	
  1. 通过 隐式类型转换
    注意这里不是赋值哦,实质为构造函数进行的初始化
	A a2 = 1; 	// 构造
  1. 使用 匿名对象 – – 生命周期只在该行
	A();

2.3 对象的大小

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

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

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

👉🔗内存对齐规则的详细讲解链接附上👈

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

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.4 this 指针

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

this 用于分辨调用对象,只有调用成员函数的时候使用,本质上就是 “成员函数” 的形参,所以对象中不存储 this 指针。当对象调用成员函数时,将 调用对象的地址作 为实参传递给 this 形参。

this指针的类型:类类型* const

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(); 					// 正常运行

🥰如果本文对你有些帮助,请给个赞或收藏,你的支持是对作者大大莫大的鼓励!!(✿◡‿◡) 欢迎评论留言~~


🔗接下篇【Ⅱ】默认成员函数篇:构造+析构、拷贝构造+赋值重载、const成员函数、取地址重载、初始化列表

👉【C++入门】类和对象Ⅱ:默认成员函数、构造函数、初始化列表、析构函数、拷贝构造函数、赋值运算符重载函数、const成员函数、取地址及const取地址操作符重载(含 时间计算器 部分实现)

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值