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.mzph.cn/web/44134.shtml

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

相关文章

7月11日学习打卡,数据结构栈

大家好呀&#xff0c;本博客目的在于记录暑假学习打卡&#xff0c;后续会整理成一个专栏&#xff0c;主要打算在暑假学习完数据结构&#xff0c;因此会发一些相关的数据结构实现的博客和一些刷的题&#xff0c;个人学习使用&#xff0c;也希望大家多多支持&#xff0c;有不足之…

dataX入门

下载dataX https://datax-opensource.oss-cn-hangzhou.aliyuncs.com/202308/datax.tar.gz 然后 下载后解压至本地某个目录&#xff0c;进入bin目录&#xff0c;即可运行同步作业&#xff1a; $ cd {YOUR_DATAX_HOME}/bin $ python datax.py {YOUR_JOB.json} 要求你有python…

vue-grid-layout详解

vue-grid-layout 教程 vue-grid-layout 是一个用于 Vue.js 的响应式拖放网格布局组件&#xff0c;允许开发者创建可调整大小、可拖放的布局&#xff0c;广泛用于仪表板、管理面板等复杂布局需求。本教程将介绍如何安装、配置和使用 vue-grid-layout。 目录 安装基本使用 布局…

怎样在 C 语言中进行类型转换?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01; &#x1f4d9;C 语言百万年薪修炼课程 通俗易懂&#xff0c;深入浅出&#xff0c;匠心打磨&#xff0c;死磕细节&#xff0c;6年迭代&#xff0c;看过的人都说好。 文章目…

暑假自律日记九

7.10 &#xff08;半小时日记打卡之——暑假第九天&#xff09; 前言&#xff1a;或许是累了&#xff0c;今天的效率和进度可以说是历史最低了&#xff0c;调休了一天&#xff0c;算是歇会 日程 八点二十起床&#xff0c;在床上赖了一会 九点二十抵达逸夫楼&#xff0c;开始补…

kafka发送消息流程

配置props.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, RoundRobinPartitioner.class); public Map<String,Object> producerConfigs(){Map<String,Object> props new HashMap<>();props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,bootstrapServers…

Spring Boot中的安全配置与实现

Spring Boot中的安全配置与实现 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天我们将深入探讨Spring Boot中的安全配置与实现&#xff0c;看看如何保护你的…

在表格中把tab换成enter键------ivx

为了方便用户输入&#xff0c;把tab键替换成enter回车 方法如下&#xff1a; 添加一个fx函数 document.addEventListener(‘keydown’, function(event) { if (event.key ‘Enter’ && !event.shiftKey) { event.preventDefault(); var focusableElements document.q…

昇思25天打卡营-mindspore-ML- Day22-应用实践-自然语言处理-LSTM+CRF序列标注

昇思25天打卡营-mindspore-ML- Day22-应用实践-自然语言处理-LSTMCRF序列标注 今天学习了 LSTMCRF 序列标注方法&#xff0c;它是一种结合了循环神经网络&#xff08;RNN&#xff09;和条件随机场&#xff08;CRF&#xff09;的强大模型&#xff0c;用于处理序列标注问题&#…

【C++BFS】690. 员工的重要性

本文涉及知识点 CBFS算法 LeetCode690. 员工的重要性 你有一个保存员工信息的数据结构&#xff0c;它包含了员工唯一的 id &#xff0c;重要度和直系下属的 id 。 给定一个员工数组 employees&#xff0c;其中&#xff1a; employees[i].id 是第 i 个员工的 ID。 employees[…

RabbitMQ 高级功能

RabbitMQ 是一个广泛使用的开源消息代理&#xff0c;它支持多种消息传递协议&#xff0c;可以在分布式系统中用于可靠的消息传递。除了基本的消息队列功能外&#xff0c;RabbitMQ 还提供了一些高级功能&#xff0c;增强了其在高可用性、扩展性和灵活性方面的能力。以下是一些主…

软件架构之嵌入式系统设计(2)

软件架构之嵌入式系统设计&#xff08;2&#xff09; 12.4 嵌入式网络系统12.4.1 现场总线网12.4.2 家庭信息网11.4.3 无线数据通信网12.4.4 嵌入式 Internet 12.5 嵌入式数据库管理系统12.5.1 使用环境的特点12.5.2 系统组成与关键技术 12.6 实时系统与嵌入式操作系统12.6.1 嵌…

MyBatis(38)MyBatis 如何与 Spring Boot 集成,有哪些实践技巧

集成MyBatis与Spring Boot可以极大地提升开发效率&#xff0c;简化配置&#xff0c;并利用Spring Boot的自动配置特性优化项目结构和性能。下面我们将详细探讨如何实现这一集成&#xff0c;并分享一些实践技巧。 1. 添加依赖 首先&#xff0c;在pom.xml中添加MyBatis和Spring…

AI学习指南机器学习篇-聚类树的剪枝

AI学习指南机器学习篇-聚类树的剪枝 在机器学习领域&#xff0c;聚类是一种常用的无监督学习方法&#xff0c;通过对数据进行分组来发现数据中的结构和模式。聚类树是一种常用的聚类算法之一&#xff0c;它通过构建一个树状结构来展示聚类的层次关系&#xff0c;并能够帮助我们…

Linux 忘记root密码,通过单用户模式修改

银河麒麟桌面操作系统 V10&#xff08;sp1&#xff09;”忘记用户密码&#xff0c;需要修改用户密码所写&#xff0c;可用于 X86 架构和 arm 架构。 2. 选择第一项&#xff0c;在上图界面按“e”键进行编辑修改。 3. 在以 linux 开头这行的行末&#xff0c;添加“init/bin/bas…

Rockchip Android平台编译生成userdata.img

Rockchip Android平台编译生成userdata.img 适用版本 本修改方法适用于Android12及以上版本 代码修改 device/rockchip/rk3576&#xff1a; --- a/rk3576_u/BoardConfig.mkb/rk3576_u/BoardConfig.mk-28,4 28,7 PRODUCT_KERNEL_CONFIG pcie_wifi.configBOARD_GSENSOR_MXC…

SSE(Server-Send-Event)服务端推送数据技术

SSE&#xff08;Server-Send-Event&#xff09;服务端推送数据技术 大家是否遇到过服务端需要主动传输数据到客户端的情况&#xff0c;目前有三种解决方案。 客户端轮询更新数据。服务端与客户端建立 Socket 连接双向通信服务端与客户建立 SSE 连接单向通信 几种方案的比较&…

【前端】fis框架学习

文章目录 1. 介绍 1. 介绍 FIS是专为解决前端开发中自动化工具、性能优化、模块化框架、开发规范、代码部署、开发流程等问题的工具框架。 使用FIS我们可以快速的完成各种前端项目的资源压缩、合并等等各种性能优化工作&#xff0c;同时FIS还提供了大量的开发辅助功能 首先我们…

Nginx上配置多个网站

一、需求描述 我们只有一台安装了Nginx的服务器,但是我们需要实现在这台服务器上部署多个网站,用以对外提供服务。 二、Nginx上配置多个网站分析 一般网站的格式为:【http://ip地址:端口号/URI】(比如:http://192.168.3.201:80),IP地址也可用域名表示;那么要实现在Nginx…

QT实现WebSocket通信

文章目录 WebSocket服务端WebSocket客户端html websocket客户端在Qt5中实现WebSocket通信可以通过使用QtWebSockets模块来实现。这个模块提供了一个WebSocket客户端和服务器的实现,可以很方便地在你的应用程序中集成WebSocket功能。 使用的时候,首先在pro工程文件中添加对应的…