C++基础(二)

目录

1.类和对象

1.1类的定义

1.2访问限定符

1.3类域

2.实例化 

 2.1实例化概念

2.2对象大小

3.this指针

4.类的默认成员函数

4.1构造函数

4.2析构函数

4.5运算符重载


1.类和对象

1.1类的定义

类的定义格式

class为定义类的关键字,Stack为类的名字,{}中为类的主体,注意定义结束时后面分号不省略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量;类中的函数称为类的方法或成员函数。

//text.cpp
#include<iostream>
using namespace std;class Stack
{//成员变量int* a;int top;int  capacity;//成员函数void Push(){}void Pop(){}
};//分号不能省略
int main()
{return 0;
}

  • 为了区分成员变量,一般习惯上成员变量会加一个特殊标识,如成员变量前面或后面加_或者m_开头。这个C++语法上并没有规定,仅凭个人或公司喜好
//为区分成员变量,一般前面加_
//成员变量
int* _a;
int _top;
int  _capacity;
  • C++中struct也可以定义类,C++兼容c中struct的用法,同时struct升级成了类,明显的变化是struct中也可以定义函数,一般情况下我们还是推荐用class定义类

  • 定义在类里面的成员默认为inline

1.2访问限定符

C++一种实现封装的方式,用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用

  • public(公有)修饰的成员在类外可以直接被访问,protected(保护)和private(私有)修饰的成员在类外不能直接被访问,protected和private是一样的
  • 访问权限作用域从该访问权限出现的位置开始直到下一个访问限定符出现为止,如果后面没有访问限定符,作用域就到 } 即类结束
//text.cpp
#include<iostream>
using namespace std;class Stack
{///void Push(){}//Push 没给限定符 class默认私有 private ///
public:void Pop(){}int Swap(){}//Pop和Swap 被public修饰,直到下一个限定符出现之前都为公有///
protected:int add();
//add 被public修饰,直到下一个限定符出现之前都为保护
/// /private:int* _a;int _top;int  _capacity;
//成员变量被private修饰,直到}结束都为私有
};
int main()
{Stack st;//公有可以访问st.Pop();st.Swap();//私有不可访问st._top;return 0;
}

额外注意: 

  1. class定义成员没有被访问限定符修饰时默认为private,struct默认为public
  2. 一般成员变量都会被限制为private/protected,需要给别人使用的成员函数会被放为public

1.3类域

类定义了一个新的作用域,类所有成员都在类的作用域中,在类体外定义成员时,需要使用  ::作用域操作符指明成员属于哪个类域

类域影响的是编译的查找规则,下面程序中Init如果不指定类域Stack,那么编译器就会把Init当成全局函数,那么编译时找不到_top等成员,就会到类域去找

//text.cpp
#include<iostream>
using namespace std;class Stack
{public:void Init(int x, int y);};
void Stack::Init(int x, int y){_top = x;_capacity = y;}
int main()
{return 0;
}

 注意:

  1. 类里面的函数声明定义分离,类创建后形成了新的类域,需要指定类域,否则不可访问

2.实例化 

 2.1实例化概念

  • 用类型在物理内存中创建的过程,称为类实例化出对象
  • 类是对象进行一种抽象描述,是一个模型一样的东西,限定了类有哪些成员变量,这些成员变量只是声明,没有分配空间,用类实例化出对象时,才会去分配空间

//text.cpp
#include<iostream>
using namespace std;class Stack
{//声明int* _a;int _top;int  _capacity;};int main()
{Stack::_top = 2024;//编译器报错,_top只是声明,并未实例化return 0;
}
  • 一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储成员变量。打个比方:类实例化出对象就像现实中使用建筑设计图造房子一样,类就像设计图,设计图规划出有多少个房间,房子大小等,但是并没有实体的建筑存在,也不能住人,用设计图修建出房子,房子才能住人。同样类就像设计图一样,只是告诉编译器即将要开多大的内存,但是不开内存,只有实例化出的对象才分配物理内存存储数据
  • ​
    //text.cpp
    #include<iostream>
    using namespace std;class Stack
    {//声明int* _a;int _top;int  _capacity;};int main()
    {Stack st;st._top=2024;
    //Stack实例化出st,系统已经给st分配内存了,可以存储数据,编译通过return 0;
    }​

    2.2对象大小

  • 分析一下类对象都有哪些成员呢?类实例化出的每个对象,都有独立的数据空间,所以对象中肯定包含成员变量,那么成员函数是否包含呢?首先函数被编译后是一段指令,对象中没法储存,这些指令存储在一个单独的区域(代码段),那么对象非要存储的话,只能是成员函数的指针。对象中是否有存储指针的必要呢,Date实例化出两个对象d1和d2,di和d2都有各自独立的成员变量_year/_month/_day存储各自的数据,但是d1和d2的成员函数Init/Print指针却是一样的,存储在对象中就浪费了。如果用Date实例化出100个对象,那么成员函数指针就重复存储100次,太浪费了。其实函数指针不需要存储的,函数指针是一个地址,调用函数被编译成汇编指令[call地址],其实编译器在编译链接时,就要找到函数的地址,不是在运行时找,只有动态多态是在运行时找,就需要存储函数地址。

内存对齐规则

  • 第一个成员在与结构体偏移量为0处的地址处
  • 其他成员变量要对齐对齐数的整数倍的地址处
  • 对齐数=编译器默认的对齐数与该成员的大小的较小值
  • VS x64平台默认对齐数是4,x86默认对齐数是8
  • 结构体总大小为:最大对齐数(所有类型变量最大者与默认对齐数取最小)的整数倍
  • 如果嵌套了结构体的情况,嵌套的结构体对齐到自己最大对齐数的整数倍,结构体整体大小就是所有最大对齐数(含嵌套结构体对齐数)的整数倍
class A
{
public:void Print(){cout << _ch << endl;}
private:char _ch;int _i;
};
//_ch 是一个字节,默认对齐数是4,最大对齐数是4,所以开辟4个字节用来存在_ch
// _i是4个字节,默认对齐数是4,最大对齐数是4,所以开辟4个字节用来存储_i
class B
{
public:void Print(){//。。。}};
class B
{};
//B和C里面没有存储任何成员变量,只有一个函数,可成员函数不存对象里面
// 按理来说是0,但是结构体怎么会没大小,为表示对象存在C++对这种规定大小为1,为了占位标识对象存在

3.this指针

编译器编译后,类的成员函数默认都会在形参第一个位置,增加一个当前类的指针,叫做this指针

例如Date类中的Init原型为 void Init(Date * const this,int year ,int month,int day),类的成员函数中访问成员变量,本质是通过this指针访问的,如Init函数中给_year赋值,this->_year=year

 原型:

class Date
{void Print(){cout << _year << "\n" << _month << "\n" << _day << endl;}void Init( int year, int month,int day){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;};

Date d1;
d1.Init(2024,7,10);d1.Print();
Date d2;d2.Init(2024, 7, 9);
d2.Print();

   真实原型     

class Date
{void Init(Date* const this,int year, int month,int day){this->_year = year;this->_month = month;this->_day = day;}void Printf(Date* const this){cout << this->_year << "\n" <<this-> _month << "\n" << this->_day << endl;}
private:int _year;int _month;int _day;};

Date d1;
d1.Init(&d1,2024,7,10);
d1.Print(&d1);Date d2;
d2.Init(&d2,2024, 7, 9);
d2.Print();

C++规定不准在实参和形参的位置写this指针(编译时编译器会处理),但是可以在函数体内显示使用this指针,this指针不能修改,但this指针指向的内容可以

this指针存在栈里

4.类的默认成员函数

默认成员函数就是用户没有显示定义,编译器会自动生成的成员函数称为默认成员函数

4.1构造函数

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名字叫构造函数,但是构造函数的主要内容并不是开辟空间创造对象(我们平常使用的局部对象是栈帧创建时,空间就开好了),而是对象实例化时初始化对象。构造函数的本质是要替代我们以前Stack和Date类中写的Init函数的功能,构造函数自动调用的特点就完美替代了Init

构造函数的特点:

  1. 函数名与类名相同
  2. 无返回值(返回值啥也不需要给,也不要写void C++就是这样规定)
  3. 对象实例化时系统会自动调用对应的构造函数
  4. 构造函数可以重载
  5. 如果类中没有显示定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显示定义编译器将不再生成

class Date
{public://1.无参构造函数Date(){_year = 1;_month = 1;_day = 1;}//2.带参构造函数Date(int year, int month, int day)
{_year = year;_month = month;_day = day;}//3.全缺省构造函数Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;};

无参构造函数,全缺省构造函数,我们不写构造时编译器默认生成的构造函数,都叫做默认构造函数。但是这三个有且只能有一个存在,不能同时存在。无参构造函数和全缺省构造函数虽然构成函数重载,但是调用时会存在歧义。注意并不是只有默认构造函数就是编译器默认生成的那就是构造函数,无参构造函数,全缺省构造函数也是默认构造函数,总结一下就是不传参就能调用

我们不写,编译器默认生成的构造,对内置类型成员变量的初始化没有要求,也就是说是是否初始化是不确定的,看编译器。

//text.cpp
#include<iostream>
using namespace std;
typedef int STDataType;
class Stack
{
public:Stack(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr == _a){perror("malloc申请失败");}_capacity = n;_top = 0;}private:STDataType* _a;size_t _capacity;size_t _top;
};
//两个Stack实现队列
class MyQueue
{
private:int size;Stack pushst;Stack popst;
};int main()
{MyQueue my;return 0;
}

 C++把类型分为自定义类型和内置类型(基本类型)。内置类型就是语言提供的原生数据类型,如int/char/double/指针等,自定义类型就是我们使用class/struct等关键字自己定义的类型。这里构造函数自动初始化,VS也将内置类型size初始化了,不同的编译器初始化值不同,C++并没有规定

对于自定义类型成员变量,要求调用这个成员变量的默认构造函数初始化。如果这个成员变量,没有默认的构造函数,那么就会报错,我们要初始化这个成员变量,需要用初始化列表才能解决

 总结:大多数情况下,构造函数都需要我们自己去实现,少数情况类似MyQueue且Stack有默认构造函数时,MyQueue自动生成就可以用

4.2析构函数

~Stack()
{free(_a);_a = nullptr;_top = _capacity = 0;
}

析构函数的特点:

1.析构函数名是在类名前面加上字符~

2.无参无返回值(与构造函数一致)

3.一个类只能有一个析构函数。若未显示定义,系统也会自动生成默认的析构函数

4.对象声明周期结束时,系统会自动调用析构函数,

5.跟构造函数类似,我们不写编译器自动生成的析构函数对内置类型成员不做处理,自定义类型成员会调用其他的析构函数

6.还需注意的是我们显示析构函数,对于自定义类型成员也会调用他的析构函数,也就是说自定义类型成员无论什么情况都会自动调用析构函数。

//text.cpp
#include<iostream>
using namespace std;
typedef int STDataType;
class Stack
{
public:Stack(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr == _a){perror("malloc申请失败");}_capacity = n;_top = 0;}~Stack(){free(_a);_a = nullptr;_top=_capacity=0;}
private:STDataType* _a;size_t _capacity;size_t _top;};
//两个Stack实现队列
class MyQueue
{public://编译器默认生成MyQueue的构造函数调用了Stack的构造,完成了两个成员的初始化//编译器默认生成MyQueue的析构函数调用了Stack的析构,释放了Stack内部的资源//显示写析构也会调用Stack的析构~MyQueue(){cout << "~MyQueue" << endl;}
private: Stack pushst;Stack popst;};int main()
{MyQueue my;return 0;
}

 MyQueue里的析构啥也没干,但是C++规定会调用其他的析构来释放内存

如果没有申请资源时,析构可以不写,直接使用编译器生成的默认析构函数,如Date,如果默认生成的析构可以用,也就不需要显示写析构如MyQueue,但是有资源申请时,一定要直接写析构,否则会造成资源泄漏如Stack

4.5运算符重载

  • 当运算符被用于类型的对象时,C++语言允许我们通过运算符重载的形式指定新的含义。C++规定类类型对象使用运算符时,必须转换成调用对应运算符重载,若没有则编译器报错
  • 运算符重载是具有特定名字的函数,他的名字是由operator和后面要定义的运算符共同构成。和其他函数一样,它也具有其返回类型和参数列表以及函数体
bool operator<(Date d1, Date d2)
{}
bool operator==(Date d1,Date d2)
{return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
}
  • 重载运算符函数的参数个数和该运算符作用的参数一样多。一元运输安抚有一个参数,二元运算符有两个参数,二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数
//text.cpp
#include<iostream>
using namespace std;class Date
{
public:Date(int year, int month, int day){_year= year;_month = month;_day = day;}int _year;int _month;int _day;};bool operator<(Date d1, Date d2)
{}
bool operator==(Date d1,Date d2)
{return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
}
int main()
{Date d1(2024, 7, 10);Date d2(2024,7,9);
//两种用法都可以d1 == d2;operator==(d1 , d2);return 0;
}
  • 如果一个重载运算符函数是成员函数,则他的第一个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数比运算对象少一个
  • 运算符重载以后,其优先级和结合性与内置类型运算保持一致
  • 不能通过连接语法中没有的符合来创建性的操作符:比如operator@

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/374960.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从零开始实现大语言模型(四):简单自注意力机制

1. 前言 理解大语言模型结构的关键在于理解自注意力机制(self-attention)。自注意力机制可以判断输入文本序列中各个token与序列中所有token之间的相关性&#xff0c;并生成包含这种相关性信息的context向量。 本文介绍一种不包含训练参数的简化版自注意力机制——简单自注意…

uni-app/vue项目如何封装全局消息提示组件

效果图&#xff1a; 第一步&#xff1a;封装组件和方法&#xff0c;采用插件式注册&#xff01; 在项目目录下新建components文件夹&#xff0c;里面放两个文件&#xff0c;分别是index.vue和index.js. index.vue&#xff1a; <template><div class"toast&quo…

【Linux杂货铺】2.进程优先级

1.进程优先级基本概念 进程优先级是操作系统中用于确定进程调度顺序的一个指标。每个进程都会被分配一个优先级&#xff0c;优先级较高的进程会在调度时优先被执行。进程优先级的设定通常根据进程的重要性、紧急程度、资源需求等因素来确定。操作系统会根据进程的优先级来决定进…

nuPlan 是一个针对自动驾驶车辆的闭环机器学习(ML-based)规划基准测试

nuPlan: A closed-loop ML-based planning benchmark for autonomous vehicles nuPlan 是一个针对自动驾驶车辆的闭环机器学习&#xff08;ML-based&#xff09;规划基准测试 Abstract In this work, we propose the world’s first closed-loop ML-based planning benchmar…

【JavaScript】解决 JavaScript 语言报错:Uncaught ReferenceError: XYZ is not defined

文章目录 一、背景介绍常见场景 二、报错信息解析三、常见原因分析1. 变量未声明2. 拼写错误3. 块级作用域4. 使用未定义的函数或对象5. 代码执行顺序 四、解决方案与预防措施1. 确保变量已声明2. 检查拼写错误3. 注意块级作用域4. 定义和调用函数5. 正确的代码执行顺序 五、示…

tkinter-TinUI-xml实战(11)多功能TinUIxml编辑器

引言 在TinUIXml简易编辑器中&#xff0c;我们通过TinUI搭建了一个简易的针对TinUIXml布局的编辑器&#xff0c;基本掌握了TinUIXml布局和TinUIXml的导入与导出。现在&#xff0c;就在此基础上&#xff0c;对编辑器进行升级。 本次升级的功能&#xff1a; 更合理的xml编辑与…

Java设计模式---(创建型模式)工厂、单例、建造者、原型

目录 前言一、工厂模式&#xff08;Factory&#xff09;1.1 工厂方法模式&#xff08;Factory Method&#xff09;1.1.1 普通工厂方法模式1.1.2 多个工厂方法模式1.1.3 静态工厂方法模式 1.2 抽象工厂模式&#xff08;Abstract Factory&#xff09; 二、单例模式&#xff08;Si…

浅析Kafka-Stream消息流式处理流程及原理

以下结合案例&#xff1a;统计消息中单词出现次数&#xff0c;来测试并说明kafka消息流式处理的执行流程 Maven依赖 <dependencies><dependency><groupId>org.apache.kafka</groupId><artifactId>kafka-streams</artifactId><exclusio…

【密码学】大整数分解问题和离散对数问题

公钥密码体制的主要思想是通过一种非对称性&#xff0c;即正向计算简单&#xff0c;逆向计算复杂的加密算法设计&#xff0c;来解决安全通信。本文介绍两种在密码学领域内最为人所熟知、应用最为广泛的数学难题——大整数分解问题与离散对数问题 一、大整数分解问题 &#xf…

thinkphp 生成邀请推广二维码,保存到服务器并接口返回给前端

根据每个人生成自己的二维码图片,接口返回二维码图片地址 生成在服务器的二维码图片 控制器 public function createUserQRcode(){$uid = input(uid);if

传言称 iPhone 16 Pro 将支持 40W 快速充电和 20W MagSafe

目前&#xff0c;iPhone 15 和 iPhone 15 Pro 机型使用合适的 USB-C 电源适配器可实现高达 27W 的峰值充电速度&#xff0c;而 Apple 和授权第三方的官方 MagSafe 充电器可以高达 15W 的功率为 iPhone 15 机型进行无线充电。所有四款 iPhone 15 机型均可使用 20W 或更高功率的电…

FPGA学习笔记(一) FPGA最小系统

文章目录 前言一、FPGA最小系统总结 前言 今天学习下FPGA的最小系统一、FPGA最小系统 FPGA最小系统与STM32最小系统类似&#xff0c;由供电电源&#xff0c;时钟电路晶振&#xff0c;复位和调试接口JTAG以及FLASH配置芯片组成&#xff0c;其与STM32最大的不同之处就是必须要有…

Appium自动化测试系列: 2. 使用Appium启动APP(真机)

历史文章&#xff1a;Appium自动化测试系列: 1. Mac安装配置Appium_mac安装appium-CSDN博客 一、准备工作 1. 安卓测试机打开调试模式&#xff0c;然后使用可以传输数据的数据线连接上你的电脑。注意&#xff1a;你的数据线一定要支持传输数据&#xff0c;有的数据线只支持充…

《数据结构:C语言实现顺序表》

文章目录 一、顺序表1、静态顺序表2、动态顺序表 二、动态顺序表实现1、创建自定义类型2、完成顺序表的创建&#xff0c;测试功能需求3、完成顺序表的初始化和销毁功能4、顺序表插入数据和打印数据5、删除数据 三、顺序表完成最终的代码test.c文件中的代码&#xff1a;用来测试…

新手教学系列——MongoDB聚合查询的进阶用法

引言 MongoDB的聚合查询是其最强大的功能之一。无论是汇总、平均值、计数等标准操作,还是处理复杂的数据集合,MongoDB的聚合框架都能提供高效且灵活的解决方案。本文将通过几个实例,详细讲解如何在实际项目中使用MongoDB进行聚合查询。 标准应用:汇总、平均值、计数等 在…

k8s集群部署mysql8主备

一、搜索mysql8版本 # helm search repo mysql# helm pull bitnami/mysql --version:11.1.2# tar -zxf mysql-11.1.2.tgz# cd mysql 二、修改value.ysqml文件 动态存储类自己提前搭建。 # helm install mysql8 -n mysql-cluster ./ -f values.yaml NAME: mysql8 LAST DEPLOYED…

Neo4j安装

下载地址&#xff1a;Neo4j Deployment Center - Graph Database & Analytics 1.安装jdk&#xff0c;Neo4j 3.0需要jdk8&#xff0c;2.3.0之前的版本建议jdk7。Neo4j最新版本5.21.2&#xff0c;对应jdk版本17 2.将下载的zip文件解压到合适路径。 3.设置环境变量NEO4J_H…

【机器学习】朴素贝叶斯算法详解与实战扩展

欢迎来到 破晓的历程的 博客 ⛺️不负时光&#xff0c;不负己✈️ 引言 朴素贝叶斯算法是一种基于概率统计的分类方法&#xff0c;它利用贝叶斯定理和特征条件独立假设来预测样本的类别。尽管其假设特征之间相互独立在现实中往往不成立&#xff0c;但朴素贝叶斯分类器因其计算…

卤味江湖中,周黑鸭究竟该抓住什么赛点?

近年来&#xff0c;卤味江湖的决斗从未停止。 随着休闲卤味、佐餐卤味等细分赛道逐渐形成&#xff0c;“卤味三巨头”&#xff08;周黑鸭、绝味食品、煌上煌&#xff09;的牌桌上有了更多新对手&#xff0c;赛道变挤了&#xff0c;“周黑鸭们”也到了转型关键期。 这个夏天&a…

linux系统操作/基本命令/vim/权限修改/用户建立

Linux的目录结构&#xff1a; 一&#xff1a;在Linux系统中&#xff0c;路径之间的层级关系&#xff0c;使用:/来表示 注意:1、开头的/表示根目录 2、后面的/表示层级关系 二&#xff1a;在windows系统中&#xff0c;路径之间的层级关系&#xff0c;使用:\来表示 注意:1、D:表示…