引入
哈喽各位铁汁们好啊,我是博主鸽芷咕《C++干货基地》是由我的襄阳家乡零食基地有感而发,不知道各位的城市有没有这种实惠又全面的零食基地呢?C++ 本身作为一门篇底层的一种语言,世面的免费课程大多都没有教明白。所以本篇专栏的内容全是干货让大家从底层了解C++,把更多的知识由抽象到简单通俗易懂。
⛳️ 推荐
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。
文章目录
- 引入
- ⛳️ 推荐
- 一、面向对象的概念
- 1.1 类的引入
- 1.2 类的定义
- 类的俩总定义方式
- 类成员的名称规范
- 二、类的访问限定符和封装
- 2.1 访问限定符
- 2.2 封装的概念
- 2.3 类的作用域
- 三、类的实例化
- 四、类的对象模型
- 4.1 类的大小如何计算?
- 空类是如何计算的?
- 4.2 类的储存方式?
- 4.3 结构体内存对齐规则
- 📝文章结语:
一、面向对象的概念
哈喽大家好啊,一眨眼我们就进入了C++ 最核心的部分了。C语言我们都知道是面向过程编程,而C++和C语言最大的不同就是面向对象编程了下面我们就来看一下面向对象编程和面向过程编程的不同把:
- 面向过程
C语言都知道是面向过程,关注的是解决问题的过程比如说我们要洗衣服
需要关注这几个过程,而C++关注的是对象来解决问题
- 面向对象
C++的面向对象总共关注的是对象:洗衣服需要
- 人、衣服、洗衣机、洗衣粉
整个过程是人把衣服放到洗衣机里倒入洗衣粉然后启动就好了
并不需要我们去了解衣服究竟是如何洗的,以及如何甩干的?只需要关注对象去完成他就好了
1.1 类的引入
在C++总是兼容C语言的,以往我们结构体是只能定义结构而不能定义函数的,在C++中把结构体升级成了类结构体内不仅可以定义变量,也可以定义函数。
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
typedef int DataType;
struct Stack
{
void Init(size_t capacity)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (nullptr == _array)
{
perror("malloc申请空间失败");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(const DataType& data)
{
// 扩容
_array[_size] = data;
++_size;
}
DataType Top()
{
return _array[_size - 1];
}
void Destroy()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
DataType* _array;
size_t _capacity;
size_t _size;
};
int main()
{
Stack s;
s.Init(10);
s.Push(1);
s.Push(2);
s.Push(3);
cout << s.Top() << endl;
s.Destroy();
return 0;
}
上面就是类引入的使用示范,以往我们函数都在结构体外面,现在结构体被升级为了类,也可以内部定义函数了。
- 而且访问时只需要加
.
就可以访问结构体内部的成员函数了
1.2 类的定义
前面说了,结构体被升级为了类。这其实只是为了兼容C语言实际上再C++中我们更喜欢使用 class
来当做类的关键字。
🍸 语法演示:
class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
🔥 class
为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。
类体中内容称为类的
成员
:类中的变量称为类的属性或成员变量
; 类中的函数称为类的方法或者成员函数
。
类的俩总定义方式
关于类的时候我们一般都是在本文件内定义或者头文件
.h
分离定义类
- 声明和定义都在类体
#define _CRT_SECURE_NO_WARNINGS 1
#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;
}
int _year;
int _month;
int _day;
};
int main()
{
Date A;
A.Init(2023,03,23);
A.Print();
return 0;
}
- 声明和定义分离
-
-
.h
文件
-
#pragma once
#include<iostream>
using namespace std;
class Date
{
public:
void Init(int year, int month, int day);
void Print();
int _year;
int _month;
int _day;
};
-
-
.c
文件
-
#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"
void Date::Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Date::Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
-
-
main
文件
-
#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"
int main()
{
Date A;
A.Init(2023,03,23);
A.Print();
return 0;
}
🔥 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
类成员的名称规范
为什么我们要专门讲一下类成员的名称规范呢?因为如果我们命名不规范就会出现下面情况:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
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 A;
A.Init(2023,03,23);
return 0;
}
这里你能看出来那个是成员变量那个是接收的值嘛?
- 所以我们一般在成员变量前面加
_name
,这样就可以有效区分成员变量了
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
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 A;
A.Init(2023,03,23);
return 0;
}
二、类的访问限定符和封装
2.1 访问限定符
类为了更加安全还给我们提供了三种访问限定来用于使用控制权限:
- 【访问限定符说明】
- public修饰的成员在类外可以直接被访问
- protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
- 如果后面没有访问限定符,作用域就到 } 即类结束。
- class的默认访问权限为private,struct为public(因为struct要兼容C)
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别
2.2 封装的概念
C++是一门面向对象语言,而java 、python 他们也是面向对象的语言,面向对象的语言一般都有最重要的三个特性性:封装、继承、多态。
- 在类和对象阶段,主要是研究类的封装特性,那什么是封装呢?
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
封装本质上是一种管理,让用户更方便使用类。比如说我们使用的电脑大家并不需要关心它是怎么组装的,只需要知道 电源
usb
鼠标
插在哪里去使用就可以了这其实就是封装的概念。
我们在定义一个类的时候成员变量就电脑里面的内容所以我们一般成员变量的访问权限一般都是私有的不允许外部直接访问成员变量
- 而成员函数就相当于我们电脑上的接口,需要什么就直接调用就好了
2.3 类的作用域
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 ::
作用域操作符指明成员属于哪个类域。
class Person
{
public:
void PrintPersonInfo();
private:
char _name[20];
char _gender[3];
int _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
cout << _name << " " << _gender << " " << _age << endl;
}
三、类的实例化
- 用类类型创建对象的过程,称为类的实例化
类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;
比如说我们在类里面定义的成员变量,其实只是对他进行了声明,只有当我们去定义这个类的时候才会生成空间。
- 其实就想我们以前定义结构体一样只有这个结构体真正定义出来才真正的生成空间了
int main()
{
Person._age = 100; // 编译失败:error C2059: 语法错误:“.”
return 0;
}
比
- 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量
- 类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图
只有使用图纸建造除真正的方式才回真正的物理空间
四、类的对象模型
4.1 类的大小如何计算?
一个类中既有类的成员变量,又有成员函数那么我们该如何计算类的大小呢?
- 以往我们计算一个类的大小都是使用 sizeof 来计算现在就来继续试试吧!
诶这里我们就可以看到这里只是计算成员变量的大小并没有去计算成员函数的存储大小?
- 其实这和我们类的储存方式有关系,仔细关注就会发现类的大小计算其实就是按照结构体的内存对齐规则来计算的
空类是如何计算的?
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Date
{
};
int main()
{
Date A;
cout << sizeof(A) << endl;
return 0;
}
这里我们看到如果是空类的大小计算是1,这个是因为空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。
-
用来证明这个类是存在的
-
大家自己去试试这个类该怎么计算呢?
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Date
{
void Print()
{
cout << "hello!" << endl;
}
};
int main()
{
Date A;
cout << sizeof(A) << endl;
return 0;
}
4.2 类的储存方式?
前面我的验证了,类的大小计算是按照结构体的内存对齐规则来存储的,那他的成员函数被储存在哪里了呢?
其实我们类里面的成员函数是,被存放在一个叫做代码区的地方了,和成员变量是单独存放的并且只有一份。
- 也就是 对象可以定义很多个但是,成员函数只有一份。
为什么要这样去设计存储模型呢?你想如果我们每定义一个对象就生成一个函数指针来存放函数地址的话那么生成1000个对象那不就要去生成1000个函数指针这样不就照成空间的浪费了嘛?
把成员函数放到公共代码区的话就只需要调用就好了,完全不会照成空间的浪费
4.3 结构体内存对齐规则
结构体的内存对齐规程以前博主专门写过一篇博客详细介绍过大家感兴趣喜欢可以去看看
- 结构体内存对齐规则
- 1. 第一个成员在与结构体偏移量为0的地址处。
- 2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
🔥 注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。VS中默认的对齐数为8
- 3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
- 4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
📝文章结语:
☁️ 看到这里了还不给博主扣个:
⛳️ 点赞
🍹收藏
⭐️ 关注
!
💛 💙 💜 ❤️ 💚💓 💗 💕 💞 💘 💖
拜托拜托这个真的很重要!
你们的点赞就是博主更新最大的动力!