一、面向对象(OOP)and 面向过程(POP)
1.面向过程(POP)
面向过程:就像做一道家常菜
想象一下,你要做一道简单的家常菜,比如番茄炒蛋。面向过程的方式就像是这样:
- 准备材料:你先把番茄、鸡蛋、油、盐等材料准备好。(这就像是编程中的变量初始化,准备好需要用到的数据。)
- 切番茄:你把番茄切成小块,放在碗里备用。(这就像是编程中的一个步骤或函数,完成一个具体的任务。)
- 打鸡蛋:你把鸡蛋打入另一个碗里,搅拌均匀。(这又是另一个步骤或函数。)
- 炒鸡蛋:你打开煤气灶,倒油入锅,等油热了,把鸡蛋液倒入锅中,翻炒几下,直到鸡蛋变成金黄色,然后盛出来。(这是另一个步骤,完成鸡蛋的炒制。)
- 炒番茄:你在同一个锅里再加一点油,等油热了,把切好的番茄块倒入锅中,翻炒几下,加点盐调味。(这是另一个步骤,完成番茄的炒制。)
- 混合炒:最后,你把炒好的鸡蛋重新倒回锅里,和番茄一起翻炒几下,让味道充分融合,然后关火,出锅。(这是最后一个步骤,完成整个菜品的制作。)
整个过程就像是一条流水线,每个步骤都是独立的,但又紧密相连,共同完成整个任务。
2.面向对象(OOP)
面向对象:就像使用一台智能厨师机
现在,假设你有一台智能厨师机,它可以自动完成番茄炒蛋的制作。这台厨师机就是一个“对象”。
- 设置厨师机:你先把番茄、鸡蛋、油、盐等材料放入厨师机的指定位置。(这就像是给对象设置属性,比如给厨师机指定要用的食材和调料。)
- 选择菜单:你在厨师机的控制面板上选择“番茄炒蛋”这个菜单。(这就像是调用对象的方法,告诉厨师机要做什么。)
- 启动厨师机:你按下启动按钮,厨师机就开始自动工作了。(这就像是执行对象的方法,厨师机开始按照预设的程序进行烹饪。)
在整个过程中,你不需要关心厨师机内部是如何工作的,只需要设置好材料,选择好菜单,然后启动它就可以了。厨师机会自动完成所有的步骤,包括切番茄、打鸡蛋、炒鸡蛋、炒番茄和混合炒,最后给你一盘美味的番茄炒蛋。
面向对象编程的好处就在于,它可以把复杂的任务分解成一系列相互关联但又独立的对象,每个对象都有自己的属性和方法,可以独立完成一部分任务。这样,我们就可以更加专注于对象之间的交互和协作,而不是陷入到繁琐的步骤和细节中去。
二、C++中的类(初步认识)
在C++的世界里,类(Class)是面向对象编程(OOP)的核心构件。它们为我们提供了一种结构化、模块化的方式来定义和操作数据及其相关行为。接下来我们将从类的基础概念出发,逐步深入探讨C++中类的特性和应用。
1.类的概念
类是一种用户定义的数据类型,它允许我们将数据(属性)和操作这些数据的方法(函数)封装在一起。类是创建对象的蓝图,而对象则是类的具体实例。
2.类的定义
class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。
类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。
写一个简单的代码展示:
// 定义一个简单的类
class Dog
{
public:
// 属性
std::string name;
int age;
// 方法(函数)
void bark()
{
std::cout << "Woof! Woof!" << std::endl;
}
};
int main()
{
// 创建类的对象
Dog myDog;
// 设置对象的属性
myDog.name = "Buddy";
myDog.age = 5;
// 调用对象的方法
myDog.bark();
return 0;
}
上面的例子中,Dog
是一个类,它有两个公共属性:name
和age
,以及一个方法:bark
。在main
函数中,我们创建了一个Dog
类的对象myDog
,并设置了它的属性,然后调用了它的方法。
我们会发现这似乎和C语言中的结构体有些相似,但它的函数(方法)居然可以封装在结构体(类)中。很神奇,我们知道C++就是起源于C语言。
所以接下来我们来探讨一下C语言中的结构体和C++中类的区别。
3.类的引入(结构体和类)
C++中的类起源于C语言中的结构体,他们之间存在着密切的联系,同时也有着显著的区别。
0x01.联系
- 起源与发展:C语言中的结构体是C++中类的雏形。C++在继承C语言的基础上,对结构体进行了扩展,引入了类的概念。可以说,C++中的类是C语言中结构体的超集或升级版本。
- 基本组成:在C语言中,结构体主要用于封装多个不同类型的数据。而在C++中,类同样可以包含数据成员(类似于结构体的成员变量)和成员函数(用于操作这些数据的方法)。因此,从某种程度上说,C++中的类在功能上包含了C语言结构体的所有特性。
- 语法兼容性:在C++中,结构体和类在语法上是兼容的。结构体可以包含成员函数和访问控制(如public、private等),这使得结构体在C++中几乎成为了一个简化的类。然而,在C语言中,结构体并不支持这些特性。
0x02.区别
- 功能扩展:C++中的类在功能上比C语言的结构体更为丰富。除了包含数据成员外,类还可以包含成员函数、构造函数、析构函数、访问控制(public、private、protected)以及继承和多态等特性。这些特性使得C++的类在面向对象编程中更加灵活和强大。
- 访问控制:C语言的结构体没有访问控制的概念,所有成员都是默认的public(即可以被外部访问)。而在C++中,类提供了严格的访问控制机制,可以指定哪些成员是public(公有)、private(私有)或protected(受保护)的。这有助于保护类的内部状态和实现细节不被外部随意访问和修改。
- 面向对象特性:C语言是面向过程的编程语言,而C++是面向对象的编程语言。因此,C++中的类不仅包含了数据成员和成员函数,还体现了面向对象的三大特性:封装、继承和多态。这些特性使得C++的类能够更好地模拟现实世界中的对象和关系,从而构建出更加复杂和灵活的程序。
以上不懂的内容,后面都会讲到。接下来我们看一下代码的区别
C语言中的结构体示例:
// 定义一个结构体
struct Student
{
char name[50];
int age;
};
void printStudent(struct Student s)
{
printf("姓名: %s, 年龄: %d\n", s.name, s.age);
}
int main()
{
struct Student stu1;
strcpy(stu1.name, "张三");
stu1.age = 20;
printStudent(stu1);
return 0;
}
C++中的类示例:
#include <iostream>
#include <string>
// 定义一个类
class Student
{
public:
std::string name;
int age;
// 成员函数
void print() const
{
std::cout << "姓名: " << name << ", 年龄: " << age << std::endl;
}
};
int main()
{
Student stu1;
stu1.name = "张三";
stu1.age = 20;
stu1.print(); // 调用成员函数来打印学生信息
return 0;
}
4.成员函数命名规则
class Date
{
public:
void Init(int year,int month,int day)
{
}
int year;
int month;
int day;
};
这时我们会发现成员变量和参数名一样,那么我们访问year的时候到底是成员变量里的,还是成员方法里的呢?
所以:
为了能够更好的区分哪个是成员变量,我们在定义成员变量名时可以给它们做一些标记:
下面是几种常见的表示为成员变量的 "风格" :
- 前面加斜杠 :char _name[10];
- 后面加斜杠: char name_[10];
- 前面加个 m (表示成员 member):char mname[10];
- …… (这里并没有明确的规定,不同的公司有不同的风格)
class Date
{
public:
void Init(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
int _year;
int _month;
int _day;
};
这样就可以明确区分开来了。
三、类的访问限定符及封装
引入:
class Date
{
//public:
void Init(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
int _year;
int _month;
int _day;
};
int main()
{
Date D1;
D1.Init(1, 2, 3);
return 0;
}
运行结果:
我们把public干掉,这段代码就出现了错误。为什么呢,C语言中的结构体好像不会这样啊?
谈到这个,那么我们这里就不得不谈一下C++中封装与访问限定符的概念了。
在C++编程中,类和对象是面向对象编程(OOP)的核心概念。类的定义不仅包含了对象的属性和方法,还通过访问限定符来控制这些属性和方法的访问权限。接下来将深入探讨C++中的访问限定符(public、protected、private)以及封装的概念。
1.访问限定符
C++中有三种访问限定符:
- public(公有):被声明为public的成员可以在类的内部和外部被访问到。
- protected(保护):被声明为protected的成员可以在类的内部被访问到,但不能在类的外部被直接访问。但是,它可以被继承该类的派生类访问。
- private(私有):被声明为private的成员只能在类的内部被访问到,不能在类的外部和派生类中被直接访问。
默认情况下,类的成员的访问级别为private,这就是我们刚才为什么会报错的原因。而结构体的成员的访问级别为public(因为struct要兼容C)。
代码演示:
class Date
{
public:
void Init(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date D1;
D1.Init(1, 2, 3);
return 0;
}
访问限定符的范围:
访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止。
如果后面没有访问限定符,作用域就到类结束为止。
注意:
访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别 。
我们一般在定义类的时候,建议明确定义访问限定符,不要用 struct / class 的默认的访问权限。
【面试题】 问题:C++中struct和class的区别是什么?
解答:C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来 定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的类 默认访问权限是private。注意:在继承和模板参数列表位置,struct和class也有区别,后序给大家介绍。
2.封装的概念
封装是面向对象编程的三大特性之一(封装、继承、多态)。封装将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。通过封装,可以保护数据不被外部直接访问和修改,提高代码的安全性和可维护性。
封装的本质是一种管理,让用户更方便地使用类,而不必理解类中繁杂的数据关系。通过封装,可以明确规定哪些成员是对外公开的,哪些是内部实现细节,从而提高了程序的模块化和封装性。
四、类的作用域和实例化
1.类定义的两种方式
声明和定义在一起
这种方式中,类的声明和定义都在同一个文件中完成,通常是在类的声明部分直接包含成员函数的实现。这种方式适用于小型项目或简单的类定义。
#include <iostream>
#include <string>
using namespace std;
class Student {
public:
void Init(const char* name, int age, int id)
{
strcpy(_name, name);
_age = age;
_id = id;
}
void Print()
{
cout<< _name << " " << _age << " " << _id << endl;
}
private:
char _name[10];
int _age;
int _id;
};
声明和定义分离
这种方式中,类的声明和定义是分离的。类的声明通常放在一个头文件(.h
或.hpp
)中,而类的定义(成员函数的实现)则放在一个源文件(.cpp
)中。这种方式适用于大型项目或复杂的类定义,有助于代码的组织和维护。
test.h
class Student
{
public:
void Init(const char* name, int age, int id);
void Print();
private:
char _name[10];
int _age;
int _id;
};
test.cpp
#include "test.h"
void Student::Init(const char* name, int age, int id)
{
strcpy(_name, name);
_age = age;
_id = id;
}
void Student::Print()
{
cout << _name << " " << _age << " " << _id << endl;
}
函数前的 :: 是什么?j接下来我们来解决这个问题。
2.类的作用域
类定义了一个新的作用域,类的所有成员都在类的作用域中。
在类外定义成员,需要使用作用域解析符 : : 来指明成员属于哪个类域。
在C++中,类的作用域与命名空间(Namespace)的作用域是相似的。它们都是为了防止命名冲突而设计的。当你定义一个类时,你实际上是在创建一个新的命名空间,这个命名空间包含了类的所有成员。
然而,与命名空间不同的是,类的作用域还包含了封装的概念。这意味着,除了通过类的接口(public
成员)进行访问外,类的内部实现(private
和protected
成员)对外部代码是不可见的。
3.类的实例化
先要说清楚的是:类本身是没有存储空间的。
通过类建立出对象,即实例化,才会有实际的存储空间
class Person
{
public:
void printper()
{
cout << name << endl;
}
private:
char* name;
};
int main()
{
Person.name = "byte";//编译报错,因为还没有实例化对象
Person P;
return 0;
}
若没有实例化对象P
这个class类是不会开辟空间的!
五、类对象模型
1.如何计算类的大小
在C++中,类的存储大小计算是一个相对复杂的过程,它涉及多个因素,包括成员变量、成员函数、静态成员、虚函数、继承关系以及内存对齐等。我们这里只举一些简单的列子。
class Person
{
public:
//显示基本信息
void Print()
{
cout << _name << "-" << _gender << "-" << _age << endl;
}
public:
char* _name; //姓名
char* _gender; //性别
int _age; //年龄
};
2.类对象的存储方式猜测
猜测一:对象中包含类的各个成员
缺陷:每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一个类创建多个对象时,每个对象中都会保存一份代码,相同的代码保存了多次,浪费空间。
猜测二:只保存成员变量,成员函数存放在公共的代码段。
对于上述两种存储方式,计算机是按照哪种方式来存储的,我们可以通过对下面的不同对象分别获取大小来进行分析:
// 类中既有成员变量,又有成员函数
class A1
{
public:
void f1() {}
private:
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;
}
通过单目操作符sizeof来获取这三个对象的大小,结果A1的大小为4个字节,A2的大小为1个字节,A3的大小也为1个字节。
结论:一个类的大小,实际就是该类中“成员变量”之和,当然也要进行内存对齐,注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类(占位)。
结构体内存对齐规则
第一个成员在与结构体变量偏移量为0的地址处。(即结构体的首地址处,即对齐到0处)
其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
结构体的总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
对齐数 = 该结构体成员变量自身的大小与编译器默认的一个对齐数的较小值。
不明白如何计算结构体大小点击这个:结构体详解
六、this指针
引入:
#include <iostream>
using namespace std;
class Date
{
public:
void Display()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
void SetDate(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1, d2;//实例化两个日期类
d1.SetDate(2020, 10, 25);//设置d1的日期
d2.SetDate(2024, 10, 26);//设置d2的日期
d1.Display();//打印d1的日期
d2.Display();//打印d2的日期
return 0;
}
运行结果:
这里我们思考一个问题:
Date 类中有 SetDate 和 Display 两个成员函数,函数体中没有关于不同对象的区分,那当 d1 调用 Display函数时,这个 Display函数是如何知道要打印 d1 对象的?而不是去打印 d2 对象呢?
地址一样,说明确实是调用的同一个函数。
这里看似有三个参数,实际上还有一个隐藏的this指针。
C++ 通过引入 this 指针解决该问题。
C++ 编译器给每个 "非静态的成员函数" 增加了一个隐藏的指针参数,
让该指针指向当前对象(函数运行时调用该函数的对象),它是系统自动生成的,
在函数体中所有成员变量的操作,都是通过该指针去访问。
只不过所有的操作对程序员来说是透明的,不需要程序员自己来传递,编译器自动帮你去完成
我们看到的:
实际调用的:
this指针的类型是类的类型的指针,而且是const(修饰的是指针指向的内容,而不是指针本身)。这意味着我们不能修改this指针的值,它只能指向对象本身。
this指针特性:
① this 指针的类型: 类类型* const
② this 指针只能在 "成员函数" 的内部使用。因此,在创建一个对象后,也不能通过对象使用this指针。
③ this 指针本质上是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给 this 形参。所以对象中不存储 this 指针。
④ this 指针是成员函数第一个隐含的指针形参,一般情况由编译器通过 ecx 寄存器自动传递,不需要用户传递。
⑤static静态成员函数不能使用this指针:静态成员函数属于类,而不属于某个对象,所以静态成员函数没有this指针。
⑥this指针的构造和清除:this指针在成员函数的开始执行前构造,在成员函数的执行结束后清除。这个过程由编译器实现,程序员不需要关心。
本篇博客到此结束,如有错误之处,望各位在评论区指正~,谢谢啦