目录
C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
C语言中,结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数
- struct stack
- {
- int* a;
- int size;
- int capacity;
- };
- void Init()
- {
- //...
- }
- void Destory()
- {
- //...
- }
- int main()
- {
- stack st;
- return 0;
- }
这是正常情况下我们去建立一个栈的代码(省略了一部分)
在C++中,我们则是优化使用了类去实现栈,更加便捷
我们可以直接去定义新的栈
- struct stack st1;
- stack st2;
同时,我们还可以把我们所需要的栈的函数去定义到我们的结构体中
- using namespace std;
- struct stack
- {
- int* a;
- int size;
- int capacity;
- void Init(int n = 4)
- {
- a = (int*)malloc(sizeof(int) * n);
- if (a == nullptr)
- {
- perror("malloc fail");
- return;
- }
- //...
- size = 0;
- capacity = n;
-
- }
- void Destory()
- {
- //...
- }
- void Push(int x)
- {
- a[size++] = x;
- }
- };
- int main()
- {
- //stack st;
- stack st1;
- st1.Init();
- st1.Push(1);
- st1.Push(2);
- st1.Push(3);
- st1.Push(4);
- return 0;
- }
如以上代码
这里的st1就是一个对象
- class className
- {
- // 类体:由成员函数和成员变量组成
-
- }; // 一定要注意后面的分号
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号。 类中的元素称为类的成员:类中的数据称为类的属性或者成员变量; 类中的函数称为类的方法或者成员函数。
我们可以简单定义一个日期,也就是年月日
- class Data
- {
- private:
- int _year;
- int _month;
- int _day;
- };
C++中一般在定义变量中都会在前边加上一个_去区分
然后我们再加一个函数去令他完善一下
- class Data
- {
- public:
- void Init(int year = 2024,int month = 1,int day = 1)
- {
- _year = year;
- _month = month;
- _day = day;
- }
- private:
- int _year;
- int _month;
- int _day;
- };
- int main()
- {
- Data d1;
- d1.Init(2024, 4, 6);
- return 0;
- }
这样我们就完成了
这里就会有人问了,这里的public和private又是什么?
这里就涉及到我们另外一个知识点:类的访问限定符及封装
C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其 接口提供给外部的用户使用。

注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别
所以用通俗易懂的话去讲就是:
private里面的基本都是存储的变量,无法去直接访问并改变,publi里面的存储的是函数,可以直接去访问并做出一定的编译
所以理解这些后,刚才的日期代码才能够去充分理解
- class Data
- {
- public:
- void Init(int year = 2024,int month = 1,int day = 1)
- {
- _year = year;
- _month = month;
- _day = day;
- }
- private:
- int _year;
- int _month;
- int _day;
- };
- int main()
- {
- Data d1;
- d1.Init(2024, 4, 6);
- return 0;
- }
在C++中定义类时,主要有两种方式:在单个文件中定义整个类(包括成员变量和成员函数),或者将类的声明和定义分别放在不同的文件中(声明定义分离)。下面将详细讨论这两种方式
声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理
- using namespace std;
- struct stack
- {
- int* a;
- int size;
- int capacity;
- void Init(int n = 4)
- {
- a = (int*)malloc(sizeof(int) * n);
- if (a == nullptr)
- {
- perror("malloc fail");
- return;
- }
- //...
- size = 0;
- capacity = n;
-
- }
- void Destory()
- {
- //...
- }
- void Push(int x)
- {
- a[size++] = x;
- }
- };
类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::
.h文件:
- class stack
- {
- public:
- void Init(int n = 4);
- void Push(int x);
- private:
- int* a;
- int size;
- int capacity;
- };
.cpp文件:
- void stack::Init(int n)
- {
- a = (int*)malloc(sizeof(int) * n);
- if (a == nullptr)
- {
- perror("malloc fail");
- return;
- }
- capacity = n;
- size = 0;
- }
- void stack::Push(int x)
- {
- //...
- }
在.cpp文件中,我们不要直接去定义Init,如果直接定义会报错

所以要在Init前边加上stack::去表明,这是这个是属于stack类域的(也就是类的作用域)
同时也要注意:传参时,我们只需要在头文件中去设置半参省或者全参省,在源文件.cpp中只需要传递所需要的参数即可,也就是int n
类的作用域也是影响了搜索规则,比如下面两个类,栈和队列都有init和push函数:
-
- class stack
- {
- public:
- void Init(int n = 4);
- void Push(int x);
- };
- class queue
- {
- public:
- void Push(int x);
- };
如果没有域的存在,就会出现冲突
所以,类本身就是一种域
面向对象的三大特性:封装、继承、多态
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行 交互。
封装本质上是一种管理:我们如何管理兵马俑呢?比如如果什么都不管,兵马俑就被随意破坏了。那么我们 首先建了一座房子把兵马俑给封装起来。但是我们目的全封装起来,不让别人看。所以我们开放了售票通 道,可以买票突破封装在合理的监管机制下进去参观。类也是一样,我们使用类数据和方法都封装到一下。 不想给别人看到的,我们使用protected/private把成员封装起来。开放一些共有的成员函数对成员合理的访 问。所以封装本质是一种管理。
封装的优势:
安全性:通过隐藏其内部状态,对象不会因为外部代码的直接访问而处于不一致的状态
简化接口:对象提供的公共方法可以是简单的接口,使用者无需了解实现细节即可使用对象
降低复杂性:将数据和操作数据的代码封装在一起有助于减少系统的复杂性
可维护性和可扩展性:封装使得修改和增加功能变得简单,因为修改的影响局限于单个对象内部,不会波及整个系统
重用性:通过封装,可以使对象更加通用,易于在不同场景下复用
用类类型创建对象的过程,称为类的实例化
- class Data
- {
- public:
- void Init(int year = 2024,int month = 1,int day = 1)
- {
- _year = year;
- _month = month;
- _day = day;
- }
- private:
- int _year;
- int _month;
- int _day;
- };
思考一下,在这个代码中,int _year; int _month; int _day;这是声明还是定义呢?
定义和声明的本质区别是是否开辟一块空间
- class Data
- {
- public:
- void Init(int year = 2024,int month = 1,int day = 1)
- {
- _year = year;
- _month = month;
- _day = day;
- }
- private:
- int _year;
- int _month;
- int _day;
- };
- int main()
- {
- Data d1;
- //d1.Init(2024, 4, 6);
- return 0;
- }
这里的Data d1;这才是定义

在这里,我们不能直接对 Data::Init进行直接访问,也无法对year,month以及day进行直接访问,因为这里的year,month以及day只是声明不是定义

但是我们可以对d1的变量进行访问
而这一步,Data d1;这就是类的实例化
类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它

类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?如何计算一个类的大 小?
- class Data
- {
- public:
- void Init(int year = 2024,int month = 1,int day = 1)
- {
- _year = year;
- _month = month;
- _day = day;
- }
- int _year;
- int _month;
- int _day;
- };
上面类的大小是多少字节?
结构体内存对齐规则:
- 第一个成员在与结构体偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 注意:对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。 VS中默认的对齐数为8
- 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
我们利用sizeof计算一下
12字节
这个12字节怎么算的呢?
如果去除掉函数的话,一共3个int元素变量,这样的话确实是12字节
是不是函数不算呢?
我们去除一下函数看一看

还是12字节
所以由此可知,这与类成员的存储结构有关,与函数无关
这里定义了一个d1,我们在定义一个d2看一看

转到编译码中可以看到,两个函数的地址相同
所以猜测一下,类的对象模型应该是什么样的???
猜测一:对象中包含类的各个成员

缺陷:每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一个类创建多个对象时,每个对象中都会保存一份代码,相同代码保存多次,浪费空间。
猜测二:只保存成员变量,成员函数存放在公共的代码段


每个成员的函数的地址都是一样的,在公共区域存放函数的代码更加的合理
- class A2 {
- public:
- void f2() {}
- };
看一下,这个类的大小是多少?

大小为一
为什么呢?
原因是:
在C++中,类的大小是由其数据成员决定的。如果一个类没有数据成员,其大小通常不会是0,因为语言标准确保每个对象都必须有一个独一无二的地址,以便能够区分不同的对象。即使一个类没有任何数据成员,编译器也会给对象分配至少一个字节的大小,以保证对象有独立的地址
所以对于A2这个类域,虽然里面没有数据成员,只有一个函数f2,但编译器也会给一个字节的空间
- class fun {
- public:
- void Print()
- {
- cout << "被调用" << endl;
- }
- };
- int main()
- {
- fun a1;
- fun* p1 = &a1;
- p1->Print();
- fun* p = nullptr;
- p->Print();
- return 0;
- }
再看一下这个代码
把p设置为空指针,这里的print还会被调用吗?

最终结果仍是被调用了
为什么呢?
这里的
Printf函数并不在指针指向的空间里面,而这里的p1,p2指向的是对象,对象里面没有函数的地址,虽然有箭头,但是并没有进行解引用
