1 什么是面向过程和面向对象
我们知道,c语言是一门面向过程的语言,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。而c++、Java则是一个面向对象的语言,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完 成。面向过程"和"面向对象"是两种不同的编程范式,它们反映了程序设计和组织代码的不同思想。
面向过程
思想:面向过程编程的核心思想是将问题划分为一系列的步骤,每个步骤都是一个函数或者是一个子过程。程序的执行按照一些特定的步骤依次进行。
面向对象
思想:面向对象编程的核心是将程序看作是对象的集合,而不是一系列步骤的集合。对象是对数据和行为的封装,具有特定的属性和方法。
1.1举例
当我们将程序描述成洗衣服这一个事情。
对于面向过程的思想来说,我们将洗衣服的每一个子过程都列举出来:准备好要洗的衣服-->准备一个盆-->放水-->放衣服-->放洗服......
对于面向对象的思想来说,我们先把整个事情划分成几个对象:人、衣服、洗衣机、洗衣粉
整个事情的过程:人将衣服丢进洗衣机、倒入洗衣粉、启动洗衣机
整个过程是由这些对象交互完成的,人不需要关心洗衣机是如何工作的。
总结:不同的思想看待问题的角度不同,实现的方式也会有差别。选择面向过程还是面向对象通常取决于问题的性质和开发者的偏好。大多数现代编程语言支持两种范式,而实际的项目中通常会根据问题的需求灵活选择使用这两种范式。
2类的引入
C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。比如: 之前在数据结构初阶中,用C语言方式实现的栈,结构体中只能定义变量;现在用C++方式实现, 会发现struct中也可以定义函数。
上面结构体的定义,在C++中更喜欢用class来代替。
3类的定义
类的定义和结构体很相似
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。 类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者 成员函数。
3.1类的两种定义方式:
1、声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
2、类声明和成员函数定义分开。类声明放在.h的头文件中,类体里面也只有成员函数的声明,将成员函数定义在.cpp源文件中。为了防止命名冲突,这样定义的成员函数需要在前面加类的名称(类似命名空间声明)。
为了代码的维护性,一般用第二种类定义方式。
4.类的访问限定符及封装
4.1访问限定符
4.1.1为什么要有访问限定符
对于一个类来说,也许会有很多的成员函数和成员属性,有些变量我们希望它能被外部直接访问,当然,大多数的变量我们都不希望外不能够直接访问,这样就太不安全了。所以我们需要使用访问限定符来划定属性和方法的访问权限,也便于我们对类的管理。
4.1.2有哪些访问限定符呢?
public(公有):使用 public 关键字标识,表示成员对外公开,可以在任何地方访问。这意味着类的外部代码可以直接访问公有成员。
protected(保护):使用 protected 关键字标识,表示成员对于类的子类是可见的,但对于类的外部代码是私有的。
private(私有):使用 private 关键字标识,表示成员仅在类的内部可见,外部代码无法直接访问。私有成员只能在类的内部使用。
说明:
访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
如果后面没有访问限定符,作用域就到 } 即类结束。
class的默认访问权限为private,struct为public(因为struct要兼容C)。
访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别、
也就是说,访问限定符只是能否访问的一道门,通过这扇门后就不会管你了。
4.1.3简单举例理解
定义以下类
外部访问public下的成员属性及方法,正常编译
外部访问private下的成员属性及方法,报错
4.1.4C++中的class与struct的区别(面试问题)
class
和 struct
之间的差异主要体现在默认的成员访问权限和继承方式上。除了这些,它们在语法上几乎是一样的
1、默认的成员访问权限:
- 在
class
中,默认的成员访问权限是private
。 - 在
struct
中,默认的成员访问权限是public
。
2、继承方式:
- 通过
class
定义的派生类,默认的继承权限是private
。 - 通过
struct
定义的派生类,默认的继承权限是public
。
对比两者我们可以发现,其实class和struct大致没有什么区别,class能做到的struct也能做到,只不过两者的默认权限有些差异,取决于具体的需求和编码风格。
4.2封装
4.2.1什么是封装?(重要)
将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来 和对象进行交互。在实现封装时,通常使用访问修饰符来设置成员的访问权限。例如,使用 private
成员来隐藏内部细节,而通过 public
成员提供对外的接口。
举例
假设实现一个电脑类,开机键实现开机操作,对于用户来说无需在意开机的具体细节,只需要知道按下去就能开机。开机键就是我们对外的接口。这样一来,用户能够更加方便的使用类,同样,避免了用户直接使用内部属性,提高了安全性。
5.类的作用域
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。
6.类的实例化
在面向对象编程中,类是一种模板或蓝图,对象是根据这个模板创建出来的具体实体。实例化是通过调用类的构造函数来创建对象的过程。用类类型创建对象的过程,称为类的实例化.
1. 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没 有分配实际的内存空间来存储它;
2. 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量
int main() { Person._age = 100; // 编译失败:error C2059: 语法错误:“.” return 0; }
Person类是没有空间的,只有Person类实例化出的对象才有具体的年龄。
7. 类对象模型
7.1如何计算类对象的大小
相对于c语言的结构体,c++的结构体可以在里面直接定义函数,这样一来有了函数的结构体的大小又怎么算呢?类的大小又怎么算呢?
观察以下代码
7.2为什么空类的大小为1?
分析A3,A3是一个空类,为什么sizeof A3等于1呢?
在C++中,一个空类(没有任何成员变量或成员函数)的大小通常为1字节。这是因为在C++中,空类的实例需要有独一无二的地址,以便在使用指针操作时能够区分不同的对象。
7.3函数在类里面的存储形式?
分析A2,A2类体只有一个函数,为什么sizeof A2等于1呢?
首先我们先思考这样一个事实。对于一个类来说,其每个实例化对象里的成员变量的值可能都不一样。但是其中的成员方法都是一样的。如果每实例化一个对象都开辟空间存放其成员函数,这样一来就会浪费很多空间。于是,c++将类中的成员函数都存储在程序的代码段(text segment)中,而不是存储在对象的实例中,这样可以节省内存空间。这意味着所有对象共享相同的成员函数代码。
简单理解就是,有一个公共区域存储该类的成员函数,每当有对象要调用其成员函数时,就去这个区域里面去调用。
这也就是为什么我们siziof A2等于1了
7.4面试题
1、结构体怎么对齐?为什么要进行内存对齐?
2、如何让结构体按照指定的对齐参数进行对齐?能否按照3、4、5即任意字节对齐?
3、什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景
之前的博客里有讲解
8.this指针
8.1this指针的引出
观察以下代码#include<iostream> using namespace std; //日期类 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(2024, 3, 2); d1.Print(); d2.Init(2024, 3, 1); d2.Print(); return 0; }
上述代码定义了一个日期类,类里面有Init和Print两个成员函数,函数体中没有对象的区分,当实例化对象d1调用Init函数时,该成员函数怎么区分是哪个对象来调用它的呢?因为之前我们已经讲过,类里面的成员函数都是放在外面的公共区域,代码只有一份,也就是说,如果不能区分是哪个对象来调用成员函数,那就会发生错误,毕竟每个对象的成员变量都是不一样的。
于是,c++通过引入一个this指针来解决类似问题。
C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
也就是说,在你定义一个成员函数的时候,这个函数的参数里面就有一个你隐式的this指针,在你实例化某个对象并调用这个成员函数的时候,这个this指针会指向该对象的地址,告诉这个函数是哪个对象在调用它。
8.2this指针的特性
1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
2. 只能在“成员函数”的内部使用
3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给 this形参。所以对象中不存储this指针。
4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
8.3关于this指针的面试题
1. this指针存在哪里?
编译器在生成程序时加入了获取对象首地址的相关代码。并把获取的首地址存放在了寄存器ECX中(VC++编译器是放在ECX中,其它编译器有可能不同)。也就是成员函数的其它参数正常都是存放在栈中。而this指针参数则是存放在寄存器中。类的静态成员函数因为没有this指针这个参数,所以类的静态成员函数也就无法调用类的非静态成员变量。
2. this指针可以为空吗?
this指针是否可以为空需要看情况,如果被调用的成员函数本身与成员属性无关,也就意味着不需要解引用this指针,不解引用this指针的话,也就随便它是个啥了。
举例:
有的朋友可能会问,t不是nullptr吗?为什么解引用还不会报错?有->符号并不意味着就解引用了,要看是否真的访问了所指空间。而上面我们已经讲过,print函数并不在类里面,也就意味着t->print()并没有在t的地址上去访问,所以即使t为空也能访问到里面的成员函数。只不过这个时候需要注意判断this指针是否可以为空。
8.4为什么静态成员函数没有this指针
静态成员函数就是被static修饰的成员函数。静态成员函数属于类而不属于类的实例化。也就是说,静态成员函数并不依赖特定的某个实例化对象,它们在调用的时候并不会传递this指针。this指针通常指向调用该成员函数的对象的地址,但是由于静态成员函数不操作特定对象,所以不也不需要this指针。
在静态成员函数内部,不能直接访问类的非静态成员变量和非静态成员函数,因为它们是对象相关的。静态成员函数只能访问类的静态成员变量和静态成员函数,这些成员是与类本身相关而不是与类的实例相关。
class MyClass { public: int x; static int y; // 静态成员函数 static void staticFunction() { // 不能访问非静态成员 x,因为没有 this 指针 // x = 10; // 错误 // 可以访问静态成员 y y = 20; } }; // 初始化静态成员 y int MyClass::y = 0; int main() { // 创建 MyClass 类的对象 MyClass obj; // 访问非静态成员 x obj.x = 5; // 调用静态成员函数 MyClass::staticFunction(); return 0; }
在上述例子中,staticFunction
是一个静态成员函数,因此在它内部不能直接访问非静态成员 x
。静态成员函数可以直接访问静态成员 y
,而不需要对象实例。
8.5思考: