前言
类和对象是C++初阶比较难的篇章,这里的学习深度还是很深的,建议学习的小伙伴比这打一下代码。
类的定义格式
概念概述
- class为定义类的关键字,Stack为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量;类中的函数称为类的方法或者成员函数。
- 为了区分成员变量,一般习惯上成员变量会加一个特殊标识,如成员变量前面或者后面加_ 或者 m 开头,注意C++中这个并不是强制的,只是一些惯例,具体看公司的要求。
- C++中struct也可以定义类,C++兼容C中struct的用法,同时struct升级成了类,明显的变化是 struct中可以定义函数,一般情况下我们还是推荐用class定义类。
- 定义在类内的成员函数默认为inline。
代码实现
//类的定义格式 //class是定义类的关键字 class MyClass { public://公有 MyClass(); ~MyClass(); private://私有 }; MyClass::MyClass()//构造函数 { } MyClass::~MyClass()//析构函数 { } //结构体struct为类的名字 //同理 struct MyStruct { public: private: };
访问限定符
概念概述
- C++一种实现封装的方式,用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
- public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访问,protected和private是一样的,以后继承章节才能体现出他们的区别。
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止,如果没有后面没有访问限定符,作用域就到 }即类结束。
- class定义成员没有被访问限定符修饰时默认为private,struct默认为public。
- 一般成员变量都会被限制为private/protected,需要给别人使用的成员函数会放为public。
访问限定符的符号
在C++中,访问限定符用于控制类成员的访问权限。主要有三种访问限定符:
public
:公开的,可以被任何外部代码访问。protected
:受保护的,可以被类本身以及其派生类访问。private
:私有的,只能被类本身访问。
访问限定符的使用public(公有的):
默认情况下,如果不指定访问限定符,成员就是公开的。公开成员可以在类的任何对象上被访问,也可以在派生类中被访问(除非被隐藏)。
class MyClass { public: int publicVar; // 公开成员,可以被任何外部代码访问 void publicFunc() { /* ... */ } // 公开成员函数 };
这里我们可以看见,类外面是可以访问私有变量的
访问限定符的使用private(私有变量):
私有成员只能被类本身访问。它们不能被外部代码访问,也不能被派生类访问。
class MyClass { private: int privateVar; // 私有成员,只能被类本身访问 void privateFunc() { /* ... */ } public: void publicFunc() { privateVar = 20; // 正确,类本身可以访问私有成员 privateFunc(); // 正确 } };
在这里我们可以看到,如果放到私有变量里面,我们可以看到类比外面是无法访问类里面的私有变量的。
访问限定符的使用protected:
- 类内部访问:类本身可以访问其
protected
成员。- 派生类访问:从该类派生出的子类也可以访问基类的
protected
成员。- 外部访问限制:类外部的代码(即不是类本身也不是派生类)不能直接访问
protected
成员。//基类 class BaseClass { protected: int protectedVar; // 受保护成员,只能被类本身和派生类访问 void protectedFunc() { /* ... */ } }; // 这里是派生类,protected修饰的可以在派生类里面访问 class DerivedClass : public BaseClass { public: void accessProtectedMembers() { protectedVar = 10; // 正确,派生类可以访问基类的受保护成员 protectedFunc(); // 正确 } };
通过使用派生类,你可以创建更加灵活和可重用的代码,同时保持代码的组织性和清晰性。
需要注意的小点:
struct
struct MyStruct { //公有成员变量 void Func() { cout << "struct 公有成员变量 " << endl; } public://公有成员变量 private://私有变量 };
class
class Date { //私有成员函数变量 void Func(); public://公有 //构造函数(全缺省构造函数) Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } private: //私有成员函数变量 int _year; int _month; int _day; };
类域
概念概述
- 类定义了一个新的作用域,类的所有成员都在类的作用域中,在类体外定义成员时,需要使用
::
作用域操作符指明成员属于哪个类域。- 类域影响的是编译的查找规则,下面程序中Init如果不指定类域Stack,那么编译器就把Init当成全局函数,那么编译时,找不到array等成员的声明/定义在哪里,就会报错。指定类域Stack,就是知道Init是成员函数,当前域找不到的array等成员,就会到类域中去查找。
放到类里面实现(内联函数)
放到类里面实现此时是不需要写inline,编译器会自动认为是内联函数,从而进行编译
在类里面的实现和使用
在类域里面的使用,我们可以称之为内联函数
这也是类的最大特点,可以直接定义函数
class Date { public://公有 //构造函数(全缺省构造函数)//内联函数 Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } void Func2(); private: //私有成员函数变量 int _year; int _month; int _day; };
类域的使用( ::)
在类外面的实现和使用
这里就涉及到,头文件和实现文件了,一般是把类放到.h文件里面,把实现放到.cpp文件里面
//.h #include<iostream> using namespace std;//取消打印折叠空间 class Date { public://公有 void Func2(); private://私有 }; //类域的调用 void Date::Func2() { cout << "类域的调用:" << endl; }
此时我们发现我们去实现Func2,需要到类域里面去访问。
当然,我们也可以把类放到头文件,把实现放到实现文件里面,这个都是很灵活的
类的对象大小(内存对齐)
这里声明一下,类的对象大小其实和C语言里面结构体内存对齐的篇章差不大多,所以可以先看一下这一篇章,其实对于对象大小的内存对齐,主要就是会对齐,知道为什么需要有内存对齐就可以了
C语言-结构体的内存对齐(重点)_结构体内存对齐-CSDN博客https://blog.csdn.net/Jason_from_China/article/details/137005772
this指针
概念概述
- Date 类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当 d1 调用 Init 和 Print 函数时,该函数是如何知道应该访问的是 d1 对象还是 d2 对象呢?那么这里就要看到 C++ 给了一个隐含的 this 指针解决这里的问题。
- 编译器编译后,类的成员函数默认都会在形参第一个位置,增加一个当前类类型的指针,叫做 this 指针。比如 Date 类的 Init 的真实原型为,void Init (Date* const this, int year, int month, int day)。
- 类的成员函数中访问成员变量,本质都是通过 this 指针访问的,如 Init 函数中给_year 赋值,this-> _year = year。
- C++ 规定不能在实参和形参的位置显式的写 this 指针 (编译时编译器会处理),但是可以在函数体内显式使用 this 指针。
this指针的具体使用
举例1:
//.h文件 class Date { public://公有 //构造函数(全缺省构造函数) Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } void Print2(); void Print1() { cout <<"类里面实现函数调用:" << _year << "/" << _month << "/" << _day << endl; } private: //私有成员函数变量 int _year; int _month; int _day; }; //类外面实现函数调用 //void Date::Print2(Date* const this) void Date::Print2() { cout << "类外面实现函数调用:" << _year << "/" << _month << "/" << _day << endl; //完全书写 std::cout << "类外面实现函数调用:" << this->_year << "/" << this->_month << "/" << this->_day << std::endl; } //.cpp的实现 #include"test.h" int main() { cout << "类的定义格式:" << endl; //初始化需要默认构造函数,默认构造函数是编译器自带的初始化,以及全缺省构造函数,以及无参数构造函数 //对象1 Date d1; d1.Print1();//这里等同于 d1.Print1(&d1);但是这里是&是不传递参数的,也是不会写的,只是类似于传递参数由this接收参数 //对象2 Date d2(2, 2, 2); d2.Print2();//这里等同于 d1.Print1(&d1);但是这里是&是不传递参数的,也是不会写的,只是类似于传递参数由this接收参数 return 0; }
简单的说就是调用函数的时候,是有一个this指针指向对象的,因为C++支持函数重载,编译器会自己找到匹配的对象,这里如何找到,就是依靠this指针
举例2:
注意事项
注意事项1:
函数传参的时候,不支持显示写
注意事项2:
使用的时候支持显示写,但是不写也会直接默认存在
注意事项3:
静态成员函数是没有this指针的
在C++中,静态成员函数是与类相关联的,而不是与类的任何特定对象相关联。因此,静态成员函数不依赖于任何对象实例,它们可以在没有创建类的对象的情况下被调用。由于静态成员函数不与任何特定的对象实例相关联,它们无法访问类的非静态成员变量或成员函数,因为这些成员需要通过 this 指针来访问。
静态成员函数是用
static
修饰的成员函数。静态成员函数(静态成员函数是用
static
修饰的成员函数。):
- 静态成员函数不属于特定的对象,它是类的一部分而不是对象的一部分。
- 可以通过类名直接调用,无需通过对象来调用,例如
ClassName::staticFunctionName()
。- 静态成员函数不能访问非静态成员变量,因为它没有
this
指针来指向特定的对象。但可以访问静态成员变量。
C++内存区域划分
声明:
这里是知识是很重要的,以我的水平是没有办法很好的讲解出来,所以作为了解放到最下面,这个知识主要是函数栈帧的知识,感兴趣的伙伴可以去查找一下,或者单纯的了解一下也是没有什么问题的
在 C++ 中,内存主要分为以下几个区域:
栈(Stack):
- 存储局部变量、函数参数等。
- 由编译器自动管理内存的分配和释放。当函数被调用时,为函数的局部变量和参数在栈上分配空间;当函数返回时,这些空间被自动释放。
- 空间相对较小,增长方向是从高地址向低地址。
堆(Heap):
- 用于动态分配内存,通过
new
、malloc
等操作符进行分配,通过delete
、free
等进行释放。- 程序员需要手动管理堆内存的分配和释放,若管理不当可能会导致内存泄漏或悬空指针等问题。
- 空间相对较大,可根据需要动态增长。
全局 / 静态存储区:
- 存储全局变量和静态变量。
- 全局变量在程序启动时分配内存,在程序结束时释放;静态变量根据其作用域在不同的时间点分配和释放内存。
常量存储区:
- 存储常量,如字符串常量、常量数值等。
- 这些常量在程序运行期间不能被修改。
代码区:
- 存储程序的二进制代码。
- 这个区域通常是只读的,防止程序的代码被意外修改。
- 这里我找了这两张图,方便了解