前言:上一篇文章我们介绍了C++入门的一些基础的语法,将了命名空间,缺省参数等。这篇文章我们就来介绍剩余的语法。

在这里插入图片描述

文章目录

  • 一,函数重载
  • 二,引用
    • 2.1引用的概念和定义
    • 2.2引用的特性
    • 2.3引用的引用场景
      • 2.3.1做函数形参,修改形参影响实参
      • 2.3.2 做函数形参时减少拷贝,提高效率
      • 2.3.3 引用做返回值类型,修改返回对象,减少拷贝提高效率
    • 2.4引用返回的坑
    • 3.1 const引用
    • 4.1指针和引用的关系

一,函数重载

函数重载:函数重载(Function Overloading)允许在同一个作用域内定义多个同名函数,但这些函数的参数列表必须不同(参数类型、数量或顺序)。编译器根据调用时传递的实参类型和数量选择最匹配的函数版本。紫

举个例子:

void func1(int x,int y)
{printf("%d %d",x,y);
}
void func2(double x,double y)
{printf("%lf %lf",x,y);
}
int main()
{int a = 10;int b = 20;double c = 10.0;double d = 20.0;//同样在调用加法函数时调用的是不同的func函数//int类型的都调用func1func1(a,b);//double类型的就调用func2func2(c,d);return 0;
}

在C语言的学习过程中,功能相同的函数由于它们的参数类型不同,我们就要再创建一个函数并用数字区别开来,从而保证编译器能区分不同函数。本质上就是函数名不能相同。这种行为一方面增加了代码的冗余度,另一方面很麻烦如果有很多不同的类型那么我们要根据不同类型创建函数从效率上来说是很低的。

C++的祖师爷本贾尼博士觉得这个问题很麻烦,每次调用都要指定func1,func2。说能不能搞一个func函数,且可以创建多个func函数函数内部的区别就是参数不同,这样每次调用只用写func就好了,具体比较什么类型的参数由func函数自己去匹配。
于是祖师爷就创造出了函数重载。

函数重载有几种情况
情况一:参数类型不同

#include<iostream>
using namespace std;
int Add(int x,int y)
{cout<<"int Add(int x,int y)"<<endl;return x+y;
}
double Add(double x,double y)
{cout<<"double Add(double x,double y)"<<endl;return x+y;
}
int main()
{int a = 10;int b = 20;double c = 10.0;double d = 20.0;//Add进行了函数重载 同名函数参数与返回类型不同Add(a, b);Add(c, d);return 0;
}

情况二:参数顺序不同

void func(int a,char b)
{cout<<"func(int a,char b)"<<endl;
}
void func(char a,int b)
{cout<<"func(char a,int b)"<<endl;
}

情况三:参数的个数不同

void func()
{cout<<"func()"<<endl;
}
void func(int a)
{cout<<"func(int a)"<<endl;
}

情况四:返回类型不同

void func(int x,int y)
{cout<<"func(int x,int y)"<<endl;
}
int func(int x,int y)
{return x+y;
}

注意,仅仅是返回值不同,参数相同的同名函数是不构成函数重载的。因为在调用函数的时候也分不清到底是调用有返回值的那个还是没有返回值的那个。

情况五,在情况三的基础上加上缺省值

void func()
{cout<<"func()"<<endl;
}
void func(int a=10)
{cout<<"func(int a)"<<endl;
}
int main()
{//传指定参数就直接调用第二个func函数func(20);//不串参数这时编译器就搞不清楚了 到底是第一个还是第二个func();return 0;
}

注意,这里如果不传参调用func函数就会存在歧义,因为不传参的话无参的func函数能调用,有缺省值的func函数也能调用,编译不清楚要调用哪个所以会报错。

特别注意:

  1. 仅仅是返回值不同,参数类型相同的同名函数不构成重载!
  2. 函数重载只发生在相同域或作用域里面,不同域的同名函数不叫函数重载!

二,引用

引用是C++中一个非常重要且使用的语法,且贯穿着整个C++的学习包括后面的STL部分都会使用到引用,所以学好引用是非常有必要的。

2.1引用的概念和定义

  • 引用是C++中一种特殊的变量类型,本质是已存在变量的别名。它与指针类似,但更安全且语法更简洁。引用必须在声明时初始化,且无法重新绑定到其他变量。
  • 在引用时使用的是C语言中的& 取地址运算符,使用方法是类型& 引用的别名=引用的对象 举个例子:
#include<iostream>
using namespace std;
int main()
{int a = 10;//给a取一个别名叫bint& b = a;//也可以给b取一个别名叫c 此时c也相当于是a的别名int& c = b;cout << "a=" << a << endl;++b;cout << "++b=" << a << endl;++c;cout << "++c=" << a << endl;cout<<"&a="<<&a<<endl;cout<<"&b="<<&b<<endl;cout<<"&c="<<&c<<endl;return 0;
}

在这里插入图片描述

通过运行结果可以看到对b和c进行加加改变的是a的值,并且它们的地址还是同一个。由此我们可以得出一个结论:引用没有开新空间,引用就是给变量起了一个新的别名,对别名的操作就是对被引用对象的操作。

2.2引用的特性

  1. 引用在定义时必须初始化:告诉那个引用它是谁的别名,这就是对引用的初始化。
#include <iostream>
using namespace std;
int main()
{int p=2;
//这样做是不行的 没初始化会报错 
//int& rp;//至少要告诉rp引用的是谁 rp找不到引用对象就报错int a = 1;
//对ra初始化 告诉ra它引用的是a
int& ra = a;
return 0;
}
  1. 一个变量可以有多个引用:就像人西游记中的孙悟空一样,有齐天大圣,弼马温,猴哥等别名,变量也一样可以有多个。
  2. 引用一旦引用了一个实体就不可能再引用其他的实体。就像一个狗子一般它一旦认定了一个主人就不会再认其他的主人了。
#include <iostream>
using namespace std;
int main()
{int a=10;int& b=a;int& c=a;int& d=a;//其中bcd都是a的别名cout << &a << endl;cout << &b << endl;cout << &c << endl;cout << &d << endl;int p=100;d=p;//这里要注意p是赋值给d 而不是d是p的别名//因为d已经是a的别名了已经有一个实体了 它就不可能是另外一个变量的别名了return 0;
}

既然引用这么重要这么有用。那我们来看看引用的引用场景。

2.3引用的引用场景

2.3.1做函数形参,修改形参影响实参

回忆一下过去在C语言中,我们通过形参来影响实参是通过指针间接来控制的,比如:

#include<stdio.h>
void swap(int*x,int*y)
{int tmp=*x;*x=*y;*y=tmp;
}
int main()
{int a=10;int b=20;printf("交换前:\n");printf("a=%d\n",a);printf("b=%d\n",b);swap(&a,&b);printf("交换后:\n");printf("a=%d\n",a);printf("b=%d\n",b);return 0;
}

在C语言阶段,我们通过形参影响实参的方式只有通过指针交换指针指向的内容从而实现实参的交换,那C++有了引用之后就可以使用引用来平替指针比如:

#include<iostream>
using namespace std;
void swap(int&x,int&y)
{int tmp=x;x=y;y=tmp;
}
int main()
{int a = 10;int b = 20;swap(a, b);cout << "a=" << a << endl;cout << "b=" << b << endl;return 0;
}

因为引用就是别名,形参中的x和y就是实参对应的别名,操作它们就相当于操作实参,所以形参的改变可以影响实参。

2.3.2 做函数形参时减少拷贝,提高效率

这个很好理解,举个例子:

int add(int x,int y)
{return x+y;
}int main()
{int a=10;int b=20;int c=add(a,b);
}

上面这段代码是我们在C语言段会写的代码,且在C语言阶段我们知道形参是实参的临时拷贝,形参会自己开空间所以形参与实参不一样。但是C++不一样:

int add(int&x,int&y)
{return x+y;
}
int main()
{int a=10;int b=20;int c=add(a,b);
}

在这里插入图片描述

2.3.3 引用做返回值类型,修改返回对象,减少拷贝提高效率

首先回顾一下我们在C语言阶段学过的返回值类型有哪些?无非就是有返回值和没有返回值。有返回值一般返回的是内置类型像int char double和struct定义的结构体
比如:

int func()
{int ret=10;//......return ret;
}
int main()
{int x=func();//尝试修改一下返回值func()=20;//10=20//被const修饰的tmp具有常量的属性不能再被修改//const int tmp = 10;//tmp = 20;return 0;
}

通过上面的代码我们来看看有返回值的传值返回本质是什么?当我们尝试去修改返回值的时候,这个时候编译器就会报错:
在这里插入图片描述
那什么情况下会报这种左操作数为左值的错误呢?将上面那一小段将20赋值给10以及给const变量赋值的代码去编译就会出现这种错误,因此我们可以推测出:传值返回传回来的是一个临时变量(因为ret出了函数栈帧会销毁 ),该变量不能被修改的原因可能是被const修饰了,具有了常量的属性不能再被修改。

了解完了传值返回,接着我们再来看看C++支持的传引用返回:

#include<iostream>
using namespace std;
typedef struct SList
{int* arr;int size;int capacity;
}SL;void SLInit(SL& sl,int n=4)
{int* tmp = (int*)malloc(sizeof(int) * n);sl.arr = tmp;sl.size = 0;sl.capacity = 4;
}void SLPush(SL& sl,int x)
{if (sl.size == sl.capacity){sl.arr = (int*)realloc(sl.arr, 2 * sl.capacity);}sl.arr[sl.size] = x;sl.size++;
}int& SLAt(SL& sl, int i)
{assert(i < sl.size);return sl.arr[i];
}int main()
{SL sl1;SLInit(sl1);SLPush(sl1, 1);SLPush(sl1, 2);SLPush(sl1, 3);//传引用返回可以对返回值进行修改SLAt(sl1, 0) += 2;return 0;

先来分析一下SLAt(sl1,0)+=2 这段代码:

在这里插入图片描述

同样在上面的分析中,我们看到传值返回是要经过拷贝成临时变量才返回的,而引用返回本质就是给变量取了一个别名不需要拷贝不会浪费空间极大的提高了效率。试想:如果返回的是一个占用内存很大的结构体,使用传值返回或者做函数形参,拷贝的时候要花费多少空间?所以从节省空间这方面来说引用还是很香的!

2.4引用返回的坑

引用返回既然这么香,那么以后是否就只使用引用就好了?当然不是,引用和指针一样是一把双刃剑用的好可以快捷高效,用不好会造成一定后果。
举个例子:

int& func()
{int ret = 0;//...return ret;
}int& fun()
{int xxx = 1000;//...return xxx;
}
int main()
{//传值返回/*int x = func();cout << x << endl;*/int& x = func();cout << x << endl;fun();cout << x << endl;//野引用对越界的空间进行访问 在vs编译器下可能查不到//因为vs编译器对越界读的行为是抽查行为int arr[10];//越界读不一定报错cout<<arr[12]<<endl;//越界写可能会报错 也有可能不会报错 vs仅在数组末尾一两个位置上设置了检查arr[20]=1;return 0;
}

在这里插入图片描述

两次打印x,我们发现x的值竟然被fun函数中的xxx给修改了,这时为什么呢?
首先,func函数是传引用返回给了x那么x就是ret的别名没跑。第二次打印的x的值与xxx一样为1000,这很难不让人联想到x可能是xxx的别名,为什么会造成这种情况呢?原因是ret是在函数栈帧中创建的,栈帧销毁后ret的空间被回收,被回收的空间被起了一个别名叫x,好巧不巧的是当fun函数执行的时候xxx分配到的空间就是原来ret的空间,此时这个空间还有一个别名叫x,所以我们打印x就是xxx的值本质上xxxx就是同一块空间。

在这里插入图片描述

这里还有一点要注意就是,上面SLAt的引用返回能够使用是因为sl1中的数组是在堆上开辟的空间,函数栈帧的销毁不影响它的销毁;只有被手动释放才会销毁所以引用返回是没有问题的!

3.1 const引用

提到const在C语言阶段就会想到被限制的问题。例如:被const修饰过的指针其指向的内容或其本身就会受到限制,那么在C++中我们就习惯称之为权限问题:

int main()
{//引用int x = 10;int& a = x;int y = 20;//权限可以缩小const int& a1 = x;//a1虽然是x的别名 但是加上const修饰后 a1就不能改了a1 = y;//权限相同const int z = 10;const int& c = z;c = x; //同样c虽然是z的别名 但是c被const修饰定死了 不能被修改 这主要是在后面函数传参的时候用到//权限不能被放大const int r = 10;int& b = r;//这是对权限的放大 这样做是不行的 因为r本身已经被限制 再给r取别名的时侯也因该对别名进行修饰 const int y=10;int z=y;//权限有没有放大?//没有 就是普通的赋值 
}

通过上面代码要注意:可以引⽤⼀个const对象,但是必须⽤const引用(相同权限下)。const引用也可以引用普通对象,因为对象的访问权限在引用过程中可以缩小,但是不能放大。

void func(const int& x)
{//这里形参要用const修饰 因为实参已经被const修饰了 不能存在权限的放大}int main()
{int x = 1;const int y = 2;double d = 1.1;const int& r1 = x;const int& r2 = y;const int& r3 = 10;const int& r4 = x * 10;//编译器会将x*10给存在一个临时变量中 再将这个临时变量赋给r4 所以r4的改变不会影响x//int& r5=d; 这样是不行的,因为d会将临时变量给r5 临时变量具有常性 如果要引用临时变量要怎么办? 加上const修饰限制权限const int& r5 = d;//这里要注意 d给r5是一个普通的赋值 func(x);func(y);func(10);func(x*10);//这里的实参是 x*10后的值func(d);return 0;
}

在这里插入图片描述

为什么要加const控制权限呢?比如上面的函数传参,如果不用const修饰那么临时变量都是传不过去的,因为临时变量具有常性不能被修改如果不加const修饰就引用就是权限放大了,所以这些都是为了以后传参更加方便。 注意:权限问题只有在指针和引用的时候才会出现!!!

4.1指针和引用的关系

首先要明确一点:C++中出现的引用就是为了优化指针在C语言上的不足;换句话说引用就是优化指针的复杂度,但却不能替代彼此。比如在链式结构和属性结构中只有指针才能改变指向引用不能所以必须使用指针。

那么引用与指针的关系到底是什么呢?

在将引用返回的坑那里的时候,代码编译有两个警告:
在这里插入图片描述
这里就有人疑问了?明明是引用返回怎么警告说是返回局部变量的地址?

我们打开反汇编代码:
在这里插入图片描述
我们看到指针与引用在汇编代码层面是极其的相似,所以不难推出引用它就是使用指针来实现的,就像上面的两个警告一样引用返回的是局部变量的地址,按照上面的分析也说得过去。

以上就是本章的全部内容啦!
最后感谢能够看到这里的读者,如果我的文章能够帮到你那我甚是荣幸,文章有任何问题都欢迎指出!制作不易还望给一个免费的三连,你们的支持就是我最大的动力!
在这里插入图片描述

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

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

相关文章

嵌入式Linux驱动开发:i.MX6ULL按键中断驱动(非阻塞IO)

嵌入式Linux驱动开发&#xff1a;i.MX6ULL按键中断驱动&#xff08;非阻塞IO&#xff09; 概述 本文档详细介绍了在i.MX6ULL开发板上实现按键中断驱动的完整过程。该驱动程序实现了非阻塞IO操作&#xff0c;允许用户空间应用程序通过poll系统调用高效地监控按键状态变化&…

从 @Schedule 到 XXL-JOB:分布式定时任务的演进与实践

从Schedule到XXL-JOB&#xff1a;分布式定时任务的演进与实践 在分布式系统中&#xff0c;定时任务是常见需求&#xff08;如数据备份、报表生成、缓存刷新等&#xff09;。Spring框架的Schedule注解虽简单易用&#xff0c;但在集群环境下存在明显局限&#xff1b;而XXL-JOB作为…

阿里云营业执照OCR接口的PHP实现与技术解析:从签名机制到企业级应用

一、阿里云营业执照OCR接口的核心技术架构 阿里云OCR服务基于深度学习模型和大规模数据训练,针对中国营业执照的版式特征(如统一社会信用代码位置、企业名称排版、经营范围换行规则等)进行了专项优化,识别准确率可达98%以上。其接口调用遵循RESTful API设计规范,采用HMAC…

AI人工智能大模型应用如何落地

AI人工智能大模型应用落地需要经过以下步骤&#xff1a; 明确应用场景和目标&#xff1a;首先需要明确AI大模型在哪个领域、解决什么问题。例如&#xff0c;在智能客服领域&#xff0c;AI大模型可以用于提高客户服务的效率和质量&#xff1b;在医学领域&#xff0c;AI大模型可以…

手写Muduo网络库核心代码2--Poller、EPollPoller详细讲解

Poller抽象层代码Muduo 网络库中的 Poller 抽象层是其事件驱动模型的核心组件之一&#xff0c;负责统一封装不同 I/O 复用机制&#xff08;如 epoll、poll&#xff09;&#xff0c;实现事件监听与分发。Poller 抽象层的作用统一 I/O 复用接口Poller 作为抽象基类&#xff0c;定…

基于MCP架构的OpenWeather API服务端设计与实现

随着微服务和模块化架构的发展&#xff0c;越来越多的系统倾向于采用可插拔、高内聚的设计模式。MCP(Modular, Collaborative,Pluggable)架构正是这样一种强调模块化、协作性和扩展性的设计思想。它允许开发者以“组件”方式组合功能&#xff0c;提升系统的灵活性与可维护性。 …

从“叠加”到“重叠”:Overlay 与 Overlap 双引擎驱动技术性能优化

在技术领域&#xff0c;“Overlay”和“Overlap”常因拼写相似被混淆&#xff0c;但二者实则代表两种截然不同的优化逻辑&#xff1a;Overlay 是“主动构建分层结构”&#xff0c;通过资源复用与隔离提升效率&#xff1b;Overlap 是“让耗时环节时间交叉”&#xff0c;通过并行…

【Vue2 ✨】 Vue2 入门之旅(六):指令与过滤器

前一篇我们学习了组件化开发。本篇将介绍 指令与过滤器&#xff0c;这是 Vue 模板语法的重要扩展&#xff0c;让页面渲染更加灵活。 目录 常见内置指令自定义指令过滤器小结 常见内置指令 Vue 提供了丰富的内置指令&#xff0c;常见的有&#xff1a; <div id"app&qu…

【随笔】【Debian】【ArchLinux】基于Debian和ArchLinux的ISO镜像和虚拟机VM的系统镜像获取安装

一、Debian Debian -- Debian 全球镜像站 阿里巴巴开源镜像站-OPSX镜像站-阿里云开发者社区 debian-cd-current-amd64-iso-cd安装包下载_开源镜像站-阿里云 清华源&#xff1a; 清华大学开源软件镜像站 | Tsinghua Open Source Mirror USTC Open Source Software Mirror 二、…

如何用 Kotlin 在 Android 手机开发一个文字游戏,并加入付费机制?

Kotlin 开发 Android 文字游戏基础框架使用 Android Studio 创建项目&#xff0c;选择 Kotlin 作为主要语言。基础游戏逻辑可通过状态机和文本解析实现&#xff1a;class GameEngine {private var currentScene: Scene loadStartingScene()fun processCommand(input: String):…

安卓开发---BaseAdapter(定制ListView的界面)

概念&#xff1a;BaseAdapter 是 Android 中最基础的适配器类&#xff0c;它是所有其他适配器&#xff08;如 ArrayAdapter、SimpleAdapter&#xff09;的父类。方法签名&#xff1a;public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter { // 获取数据…

Conda配置完全指南:Windows系统Anaconda/Miniconda的安装、配置、基础使用、清理缓存空间和Pycharm/VSCode配置指南

本文同步发布在个人博客&#xff1a; Conda配置完全指南Conda 是一个开源的跨平台包管理与环境管理工具&#xff0c;广泛应用于数据科学、机器学习及 Python 开发领域。它不仅能帮助用户快速安装、更新和卸载第三方库&#xff0c;还能创建相互隔离的虚拟环境&#xff0c;解决不…

什么是账号矩阵?如何避免账号IP关联风险

账号矩阵是指在同一平台或多个平台上&#xff0c;围绕同一品牌、业务或个人 IP 构建的多个相互关联、协同运作的账号体系。这些账号通过差异化的内容定位和运营策略形成互补&#xff0c;共同实现流量聚合、品牌曝光或业务拓展的目标。协同效应&#xff1a;账号间通过内容互推、…

解析ELK(filebeat+logstash+elasticsearch+kibana)日志系统原理以及k8s集群日志采集过程

ELK日志系统解析 ELK 日志系统&#xff08;现常称为 Elastic Stack&#xff0c;由 Filebeat、Logstash、Elasticsearch、Kibana 组成&#xff09;是一套用于 日志收集、清洗、存储、检索和可视化 的开源解决方案。 它的核心价值是将分散在多台服务器 / 应用中的日志 “汇聚成池…

python 内置函数 sort() 复杂度分析笔记

在做 280. 摆动排序 时&#xff0c;有一版 python 题解&#xff0c;里面直接用了sort() &#xff0c;又用了一个简单的 for 循环&#xff0c;但整体时间复杂度为 O(n⋅log(n)) &#xff0c;那么问题就出自这个 sort() &#xff0c;所以在这分析一下 sort() 的复杂度。Python 的…

【光照】Unity中的[经验模型]

【从UnityURP开始探索游戏渲染】专栏-直达 图形学第一定律&#xff1a;“看起来对就对” URP光照模型发展史 ‌2018年‌&#xff1a;URP首次发布&#xff08;原LWRP&#xff09;&#xff0c;继承传统前向渲染的Blinn-Phong简化版‌2019年‌&#xff1a;URP 7.x引入Basic Shade…

uniapp小程序使用自定义底部tabbar,并根据用户类型动态切换tabbar数据

1.注意点 在pages.json中配置tabbar如下字段&#xff1a;custom&#xff1a;true &#xff0c;会自动隐藏原生tabbar&#xff0c;使用自定义的tabbar2.如何自定义呢 可以使用第三方组件库的tabbar组件&#xff0c;然后二次封装下内部封装逻辑&#xff1a; 1.点击切换逻辑 2.根据…

Redis 哨兵 (基于 Docker)

目录 1. 基本概念 2. 安装部署 (基于 Docker) 2.1 使用 docker 获取 redis 镜像 2.2 编排 主从节点 2.3 编排 redis-sentinel 节点 3. 重新选举 4. 选举原理 5. 总结 1. 基本概念 名词 逻辑结构物理结构主节点Reids 主服务一个独立的 redis-server 进程从节点Redis 从…

Python学习-day4

Python 语言的运算符: 算术运算符比较&#xff08;关系&#xff09;运算符赋值运算符逻辑运算符位运算符成员运算符身份运算符运算符优先级 算术运算符 定义变量a 21&#xff0c;变量b 10。运算符描述实例加 - 两个对象相加a b 输出结果 31-减 - 得到负数或是一个数减去另一…

Vite 插件 @vitejs/plugin-legacy 深度解析:旧浏览器兼容指南

&#x1f4d6; 简介 vitejs/plugin-legacy 是 Vite 官方提供的兼容性插件&#xff0c;专门用于为现代浏览器构建的应用程序提供对旧版浏览器的支持。该插件通过自动生成兼容性代码和 polyfill&#xff0c;确保您的应用能够在 IE 11 等旧版浏览器中正常运行。 核心价值 向后兼…