类和对象(上)

我们常说c++是面向对象的,c语言是面向过程的,那么我们就来看看二者的区别

目录

1.类的认知

2.类的引入

3.类的定义

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

5.类的作用域

6.类的实例化

7.类对象模型

7.1计算大小

7.3内存对齐规则

8.this指针

8.2 this指针的特性

this指针的面试题

9. C++和C语言实现Stack 对比


1.类的认知

C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。

C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完
成。拿洗衣服来举例好了。

这是洗衣服的步骤,可以理解为c语言的每个步骤都需要调用不同的函数去执行。

而我们的c++就更直接,就想公司里的工作分配,我需要你做你分内的事,但是怎么做我不管,最终我的部分加上你的部分,我们就能合力完成一件事,这也就是之前所说的函数重载或者重命名为什么问题严重的原因,因为实际工作中很容易出现这种情况。

2.类的引入

C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。比如:之前在数据结构初阶中,用C语言方式实现的栈,结构体中只能定义变量;现在以C++方式实现,会发现struct中也可以定义函数。

3.类的定义

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

class为定义类的关键字,ClassName为类的名字,{}中为类的主体,

注意类定义结束时后面分号不能省略。
1.类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。

2 . 为了区分成员变量 , 一般习惯上成员变量会加上 一个特殊标识 , 如成员变量前面或者后面加 _ 或者 m 开头 , 注意C++中这个并不是强制的 , 只是一个惯例 , 具体的看公司的要求 。

3 . C++中struct 也可以定义类 , c++ 兼容C中struct 的用法 , 同时 struct 升级成了 类 , 明显的变化是 struct 可以定义函数 , 一般情况下我们还是推荐用class定义类

4 . 定义在类 的成员函数默认为 inline 。

类的两种定义方式:

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

2. 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::

一般情况下,更期望采用第二种方式。我们在这里放一张思维导图总结一下

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

什么是封装:将类中的成员变量/函数通过针对性的对外隐藏所实现的一种隐藏细节的管理方式

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

这里可能看的云里雾里,没关系,我们梳理一下:

1.首先,封装这种管理方式是在类中进行的,也就是说明类也是一种域,类定义了一个新的作用域,我们称为"类域",如果要想在类外定义成员,需要用到符号"::" 来进行指定。
 

2.其次,封装这种管理方式是利用访问限定符来实现的。
 

3.类遵循面向对象的封装思想:即通过类,合适的隐藏对象的属性和实现细节,仅对外公开一定的接口来实现和其他对象的交互。

我们来认识一下这些访问限定符

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

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

3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止。

4. 如果后面没有访问限定符,作用域就到 } 即类结束。

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

6 . 一般成员变量都会被限制为private/protected , 需要给被人使用的成员函数会放为public 。

那么,说了这么多,封装的意义是什么??

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

5.类的作用域

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

2 . 类域影响的是编译的查找规则 , 下面程序中 Init 如果不指定类域Stack , 那么编译器就会把Init 当成全局函数 , 那么编译时 , 找不到 array 等成员的声明 / 定义在哪里 , 就会报错 。 指定类域 Stack , 就是直到 Init 是成员函数 , 当前域找不到 array 等成员 , 就会到类域去查找 。

3.不同的类可以定义同一个函数/变量 , 并不会冲突 ,   因为作用域不同 ,   类定义了一个新的作用域 --- 类域 ,类 与 类之间的作用域隔离 。

类的声明 : 需要注意的是 --> 成员函数只声明 , 不定义 , 并且如果需要加缺省参数 , 只能在声明中添加 。

类的定义 : 使用 某类的成员时 , 需要指明类域 。不指明类域时 , 编译查找规则是( 局部域 --> 全局域) , 当指明作用域时 , 编译查找规则是 ( 局部域 --> 类域 --> 全局域 )

6.类的实例化

用类类型创建对象的过程,称为类的实例化。

1. 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;比如:入学时填写的学生信息表,表格就可以看成是一个类,来描述具体学生信息。

2. 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量

类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图

这里的Person类是没有空间的,只有Person类实例化出的对象才有具体的空间。

对于变量声明和定义区别 : 是否开辟空间

7.类对象模型

7.1计算大小

只计算成员变量 , 不计算成员函数 , 遵循内存对齐 ,如果是空类 --- 1 byte ;

7.3内存对齐规则

1.  结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处

2.  其他成员变量要对齐到某个数字(对齐数)的整数倍的地址数

对齐数  =  编译器默认的一个对齐数 与 该成员变量大小的较小值 

-vs 中默认为8

-Linux中gcc 没有默认对齐数,对齐数就是成员自身的大小

3.  结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍。

 4.  如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。

8.this指针

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

对于上述类,有这样的一个问题:

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

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

8.2 this指针的特性

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

this指针的面试题

1.this指针存在哪里?

this 指针本身是一个 局部变量,它的存储位置取决于函数的调用方式:

普通成员函数:this 的存储位置取决于调用约定和编译器优化,现代编译器(尤其是64位系统)通常会优先使用寄存器传递this 先存储在寄存器上,溢出的情况下存储再是在 栈(Stack) 上(作为函数的隐式参数)。
静态成员函数:没有 this 指针(因为静态函数不绑定到对象)。

有些编译器会把 this指针存放在寄存器 , VS是通过ecx 传递this指针;存储体系里寄存器时最快的 , 然后是缓存 ,再到内存 。

2.this指针可以为空吗?

当通过 空指针调用成员函数 时,this 会被传递为 nullptr。

3.

// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{ 
public:
    void PrintA() 
   {
        cout<<_a<<endl;
   }
private:
 int _a;
};
int main()
{
    A* p = nullptr;
    p->PrintA();
    return 0;
}

答案是B

4.

// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
 void Print()
 {
 cout << "Print()" << endl;
 }
private:
 int _a;
};
int main()
{
 A* p = nullptr;
 p->Print();
 return 0;
}

答案是C。

那么这两个代码的this都是空,为什么下面这个可以而上面那个不可以呢?

代码1:崩溃 → 因为解引用 nullptr。
代码2:不崩溃 → 侥幸未触发解引用,但仍属未定义行为。
核心区别:是否通过 this 访问了成员变量。

这个先不做深入了解,在智能指针那块就会理解。

9. C++和C语言实现Stack 对比

1 . C++中数据和函数都放到了类里面 , 通过访问限定符进行了限制 , 不能再随意通过对象直接修改数据 , 这是C++封装的一种体现 , 这个是最重要的变化 。 这里的封装本质是一种更严格规范的管理 , 避免出现乱访问乱修改的问题 , 后续会继续学习封装的思想 。

2 . C++中有一些相对方便的语法 , 比如:

1 ) Init 给的缺省参数会方便很多

2 )成员函数每次不需要传对象地址 ,因为 this 指针隐含传递了

3 ) 使用类型不再需要typedef ,可以直接使用类名就很方便

3 . 后续更新的STL中 , 用适配器实现的Stack 会深刻体会到 c++ 的魅力 。

总的来说C++中通过类可以将数据 以及 操作数据的方法进行完美结合,通过访问权限可以控制那些方法在类外可以被调用,即封装,在使用时就像使用自己的成员一样,更符合人类对一件事物的认知。而且每个方法不需要传递Stack*的参数了,编译器编译之后该参数会自动还原,即C++中 Stack * 参数是编译器维护的,C语言中需用用户自己维护。

好了

如果你觉得对你有帮助,可以点赞关注加收藏,感谢您的阅读,我们下一篇文章再见。

一步步来,总会学会的,首先要懂思路,才能有东西写。

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值