Cpp快速入门语法(下)(2)

文章目录

  • 前言
  • 一、函数重载
    • 概念与使用
    • C++为何支持函数重载?
  • 二、引用
    • 概念
    • 语法
    • 特性
    • 权限(常引用)
    • 使用场景
    • 与指针的区别
  • 三、内联函数
  • 四、auto关键字(C++11)
  • 五、基于范围的for循环(C++11)
  • 六、指针空值nullptr(C++11)
  • 总结


前言

承前启后,正文开始!


一、函数重载

概念与使用

  函数重载是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,常用来处理实现功能类似数据类型不同的问题,而C语言不允许同名函数
  但是需要满足的条件是:函数的形参列表不同,即参数个数,类型,类型顺序不同

  在C语言中,我们如果要实现两数之和 Add 函数,如果需要int、double两种各一个,我们可能会命名为Addi、Addd,这很麻烦,而函数重载就可以解决这个问题,下面让我们来看具体实现代码:

#include<iostream>
using namespace std;// 1、参数类型不同
int Add(int x, int y)
{return x + y;
}
double Add(double x, double y)
{return x + y;
}// 2、参数个数不同
void f()
{cout << "f()" << endl;
}
void f(int a)
{cout << "f(a)" << endl;
}// 3、参数类型顺序不同(本质还是参数类型不同)
void f(int a, char b)
{cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{cout << "f(char b, int a)" << endl;
}int main()
{// 都可以对应到正确的函数Add(10, 20); Add(10.1, 20.2); f();f(10);f(10, 'a');f('a', 10);return 0;
}

另外你需要注意,只有返回类型不同不构成重载,原因会产生歧义,具体看以下代码

#include <iostream>
using namespace std;void f()
{cout << "void f()" << endl;
}
int f()
{cout << "int f()" << endl;return 0;
}int main()
{f(); // 调用哪一个不确定return 0;
}

C++为何支持函数重载?

  这里我们就需要回想前面学习C的时候有关预处理和编译的内容了
  在C/C++,程序运行之前,需要进行以下几个阶段: 预处理、编译、汇编、链接

关于链接,你可以尝试回想以下:

我们知道,在编译阶段会将程序中的每个源文件的全局范围的变量符号分别进行汇总。在汇编阶段会给每个源文件汇总出来的符号分配一个地址(若符号只是一个声明,则给其分配一个无意义的地址),然后分别生成一个符号表。最后在链接期间会将每个源文件的符号表进行合并,若不同源文件的符号表中出现了相同的符号,则取合法的地址为合并后的地址(重定位)

举个例子,我们观看下面两个同根.c文件内容:

	// sum.cint sum(int num1, int num2){return num1 + num2;}// main.cextern int sum(int num1, int num2);int main(){sum(1,2);return 0;}

  注意,在链接前两个.c文件都是单线不交互的,这时候,sum.c里面的sum函数有定义,而main.c里面的sum函数没有定义,等到两个.c文件经过汇编后,main.o形成如下符号表:

main 0x100
sum 0x000 (无意义的地址)

sum.o形成以下符号表

sum 0x800 (有意义的地址)

  接着,两个文件合成一个文件,错误的sum地址被改为正确的地址,而你想,假如有两个sum函数被定义,即有地址,那么它们单独来看都是有意义的地址,可是这时候要重定位哪个?哪怕只有一个文件,两个重名函数,那么你call的是哪个函数,这很明显有歧义

来验证一下吧,首先我们在Linux环境下采用gcc编译器
在这里插入图片描述
在这里插入图片描述
可以看到,Add就是Add,func就是func,没有半点修饰

接着我们再在Linux环境下采用g++编译器来编译
在这里插入图片描述

多试几个函数,其实你会发现修饰函数名字在此环境下的规律为 { _Z + 函数名长度 + 函数名 + 类型首字母 }

  也就是说,C++在进行符号汇总时,对函数的名字修饰做了改动,函数汇总出的符号不再单单是函数的函数名,而是通过其参数的类型和个数以及顺序等信息汇总出一个名字,这样一来,就算是函数名相同的函数,只要其参数的类型或参数的个数或参数的顺序不同,那么汇总出来的符号也就不同了,其实也从侧面说明了函数重载跟返回类型没关系

这可能很抽象,毕竟有关编译甚至在大学还有专门的一门专业课《编译原理》,大家如有困惑可以自行查阅其他相关资料

二、引用

概念

  引用不是定义一个变量,而是已存在的变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间

“李逵”、“铁牛”、“黑旋风”本质上都是一个人

语法

  类型说明符& 引用对象名 =引用实体(引用类型必须和引用实体是同种类型)

来个具体例子:

void TestPef()
{int a = 10;int& pa = a; // pa是a的别名// 从地址上,可以得出它和它引用的变量共用同一块内存空间printf("&a == %p\n", &a);printf("&pa == %p\n", &pa);
}

输出结果如下:
在这里插入图片描述

特性

  1. 引用在定义时必须初始化
int a = 10;
int& b = a; // right
  1. 一个变量可以有多个引用
int a = 10;
int& b = a; // right
int& c = a; // right
int& d = a; // right
  1. 引用一旦引用了一个实体,就不能再引用其他实体
	int a = 10;int& b = a;int c = 20;b = c; //你的想法:让b转而引用c,其实是c赋值给b

权限(常引用)

  我们知道,权限可以缩小或者平移,但是绝对不能放大

void TestConstRef()
{int a=0;int& b=a;const int& c=a; //支持->权限缩小const int x=10;int& y=x;//不支持-权限放大(此时的x只有读权限,没有写权限)const int& y=x;//支持权限相等//表达式的返回值是临时对象,而临时对象具有常性!!int& n = a+x = 临时对象 //这里是属于权限放大const int& n = a+x = 临时对象; //支持权限相等

使用场景

  1. 用作形参,因为是同一块内存空间,所以在一定程度上可以替代指针
//交换函数
void Swap(int& a, int& b)
{int tmp = a;a = b;b = tmp;
}
  1. 不用创建临时变量,提高效率
#include <ctime>
#include <iostream>
using namespace std;struct A { int a[10000]; };void TestFunc1(struct A& a) {}
void TestFunc2(struct A a) {}int main()
{A a;size_t begin1 = clock();for (int i = 0; i < 10000; i++)TestFunc1(a);size_t end1 = clock();size_t begin2 = clock();for (int i = 0; i < 10000; i++)TestFunc2(a);size_t end2 = clock();// 在某次错误时cout << "TestFunc1(struct A& a):" << end1 - begin1 << endl; // 0cout << "TestFunc1(struct A a):" << end2 - begin2 << endl; // 5return 0;
}

与指针的区别

  其实,引用不可像指针那样更改,注定了无法完全替代指针,像链表我们就必须用到指针

  在语法概念上,引用是一个别名,没有独立空间,同其引用实体共用同一块空间,但是在底层实现上,实际引用是有开辟空间的,由于引用是按照指针方式实现
在这里插入图片描述

总而言之,你需要记住以下几点:

1、引用在定义时必须初始化,指针没有要求。
2、引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体。
3、没有NULL引用,但有NULL指针。
4、在sizeof中的含义不同:引用的结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)。
5、引用进行自增操作就相当于实体增加1,而指针进行自增操作是指针向后偏移一个类型的大小。
6、有多级指针,但是没有多级引用。
7、访问实体的方式不同,指针需要显示解引用,而引用是编译器自己处理。
8、引用比指针使用起来相对更安全。

三、内联函数

  在C语言中,假设有一些小而频繁使用的函数如交换函数Swap,大量使用会建立栈帧,消耗时间,宏是C语言给出的解决方式,可这样太麻烦且易错

比如来个Add函数,宏的正确写法是 #define Add(x, y) ((x) + (y))

  基于此,对于C++来说,以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数的使用可以提升程序的运行效率

事实上,C++相当不鼓励使用宏,理由有代码可读性差(导致调试不方便)、与函数相比没有类型检查(宏做的仅仅是替换),在有些场景下比较复杂(需要谨慎替换后运算符的优先级)等

而C++给出的方案是:
i, 用const和enum替代宏常量;
ii,用inline(内联函数)替代宏函数

还是来个具体例子吧,我们现在来观察调用普通函数和内联函数的汇编代码来进一步查看其优势:

int Add(int a, int b)
{return a + b;
}
int main()
{int ret = Add(1, 2);return 0;
}

在这里插入图片描述
  如果内联函数语句较多且多次不同地方调用,可能会使编译后的文件(可执行程序)变大,其实,这本质上就是一种以空间换时间的做法,但优点是减少了调用开销,提高了程序运行效率

  内联函数是对编译器的一个建议,对于我们实现的内联函数,编译器不一定执行,不同编译器关于inline函数得实现机制可能不同;一般情况下,建议将函数规模较小,不是递归且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性;

  inline函数不要让声明和定义分离,分离会导致链接错误;因为inline被展开,就不再调用函数,没有函数地址了,链接就会找不到

四、auto关键字(C++11)

  随着学习的深入,我们会发现1. 类型难于拼写 2. 含义不明确导致容易出错
  auto在C11就因此被赋予了新的含义:作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得

  1. 用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时必须加&
#include <iostream>
using namespace std;int main()
{int a = 10;auto b = &a;   // 自动推导出b的类型为int*auto* c = &a;  // 自动推导出c的类型为int*auto& d = a;   // 自动推导出d的类型为int// 打印变量b,c,d的类型cout << typeid(b).name() << endl;// 打印结果为int*cout << typeid(c).name() << endl;// 打印结果为int*cout << typeid(d).name() << endl;// 打印结果为intreturn 0;
}
  1. 在同一行定义多个变量必须是同一类型
int main()
{auto a = 1, b = 2; // rightauto c = 3, d = 4.0; // err: “auto”必须始终推导为同一类型return 0;
}
  1. auto不能作为函数的参数
void TestAuto(auto x) {} // err
  1. auto不能直接用来声明数组
int main()
{int a[] = { 1, 2, 3 };auto b[] = { 4, 5, 6 };// errreturn 0;
}

五、基于范围的for循环(C++11)

  C++11中引入了基于范围的for循环。for循环后的括号由冒号分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。

其实是抄的Python的作业

	int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//将数组元素值全部乘以2for (auto& e : arr) // 运用了引用{e *= 2;}//打印数组中的所有元素for (auto e : arr){cout << e << " ";}cout << endl;

范围for的使用是有条件的:

一、for循环迭代的范围必须是确定的
 对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。
二、迭代的对象要实现++和==操作
 这是关于迭代器的问题,大家先了解一下。

六、指针空值nullptr(C++11)

  前人挖坑,NULL其实是一个宏,在传统的C头文件(stddef.h)中可以看到如下代码:

/* Define NULL pointer value */
#ifndef NULL
#ifdef __cplusplus
#define NULL    0 // NULL 直接被替换为0
#else  /* __cplusplus */
#define NULL    ((void *)0)
#endif  /* __cplusplus */
#endif  /* NULL */

  我们之前都拿NULL当指针空值,而上述错误就可能导致以下BUG:

#include <iostream>
using namespace std;void f(int)
{cout << "f(int)" << endl;
}
void f(int*)
{cout << "f(int*)" << endl;
}
int main()
{f(0);f(NULL); // 我们想的是匹配第二个,结果是第一个,这就是错误的宏替换带来的后果f((int*)NULL);return 0;
}

所以,对于C++98中的问题,C++11引入了关键字nullptr

请注意:

  1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为关键字引入的
  2. 在C++11中,sizeof(nullptr)与sizeof((void*)0)所占的字节数相同
  3. 为了提高代码的健壮性,在后序表示指针空值时建议最好使用nullptr

总结

  本节干货好多,函数重载原理的那一部分可能有些困难,加油!

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【BFS专题】— 解决拓扑排序问题

拓扑排序介绍&#xff1a; 1、课程表 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 通过Map<Integer, List<Integer>> 来创建邻接图&#xff0c;数组来表示入度然后遍历课程数组&#xff0c;建图然后再拓扑排序&#xff0c;bfs最后在遍历入度数组&…

14届蓝桥杯嵌入式国赛

目录 前言&#xff1a; 1.使用CUbeMX进行基础初始化配置 &#xff08;1&#xff09;选则芯片与基本初始化 &#xff08;2&#xff09;LED配置 &#xff08;3&#xff09;按键配置 &#xff08;4&#xff09;定时器和PWM以及频率 &#xff08;5&#xff09;ADC电压检测 …

计算机网络 --- 初识协议

序言 上一篇文章中 &#xff08;&#x1f449;点击查看&#xff09;&#xff0c;我们简单的了解了怎么寻找目标计算机&#xff0c;需要通过交换机&#xff0c;路由器等设备跨越多个网络来不断的转发我们需要传输的数据&#xff0c;直至到达目标计算机。  那我们设备之间数据是…

JMeter 中使用 Gson 操作请求中的Boby参数

背景 使用org.json.JSONObject 转换&#xff0c;与原Body参数顺序发生变化&#xff0c;原因&#xff1a;JSONObject内部是用Hashmap来存储的&#xff0c;本质上是一个无序的键值对集合&#xff0c;不应依赖字段的添加顺序。 为解决org.json.JSONObject 输出顺序问题&#xff…

鸿蒙读书笔记2:《鸿蒙操作系统设计原理与架构》

2. OS基础平台部件化 &#xff08;1&#xff09;内核层 内核层包括内核部件和HDF驱动框架部件。当前已提供LiteOS-M、 LiteOS-A、Linux和UniProton这4种内核部件&#xff0c;未来还可增加更多类 型的内核部件。LiteOS、Linux内核部件可以按需部署在不同设备之 上&#xff0c;内…

echarts X轴文本太长 formatter自定义文本的显示方式

如果ECharts中X轴的文本太长&#xff0c;可以通过设置axisLabel的rotate属性来旋转标签&#xff0c;或者使用formatter函数来自定义文本的显示方式。另外&#xff0c;可以开启axisLabel的interval属性来控制显示的标签的间隔。 option {tooltip: {},xAxis: {type: category,d…

p14 使用阿里云服务器的docker部署NGINX

拉取NGINX的镜像 这里因为之前已经配置过从阿里云的镜像仓库里面拿镜像所以这里直接就执行docker pull nginx拉取NGINX镜像就OK了 运行NGINX镜像 这里执行docker run -d --name nginx01 -p 3344:80 nginx这里3344是服务器访问的端口80是容器内部的端口&#xff0c;可以看到…

【C++ Primer Plus习题】16.5

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: #include <iostream> #include <list> using namespace…

硬件工程师笔试面试——集成电路

目录 17、集成电路 17.1 基础 集成电路实物图 17.1.1 概念 17.1.2 集成电路的发展历程 17.1.3 集成电路的分类 17.1.4 集成电路的制造工艺 17.1.5 集成电路的应用 17.2 相关问题 17.2.1 集成电路的制造工艺中,光刻技术是如何实现的? 17.2.2 在集成电路设计中,如何…

微信电脑版聊天图片DAT格式文件转为普通JPG图片

1-7 本文章主要教你如何恢复微信聊天中的聊天图片&#xff0c;主要应用场景是&#xff0c;当你的微信被封号了&#xff0c;或者无法登录了&#xff0c;会导致微信聊天中的聊天图片没办法再打开&#xff0c;如果是重要的图片&#xff0c;那就有损失了&#xff0c;所以有了本文的…

【无人机设计与控制】四旋翼无人机轨迹跟踪及避障Matlab代码

摘要 本文主要研究了四旋翼无人机在复杂环境中的轨迹跟踪与避障控制策略。通过Matlab/Simulink对四旋翼无人机进行了建模与仿真。系统集成了避障算法&#xff0c;使得无人机在执行任务时能够有效避开障碍物&#xff0c;保证飞行的安全性与稳定性。 理论 无人机飞行控制通常涉…

leetcode-枚举算法

1.两数之和 题目一&#xff1a;两数之和 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案&#xff0c;并且你不能使用两次相同的元素…

Java项目实战II基于Spring Boot的宠物商城网站设计与实现

目录 一、前言 二、技术介绍 三、系统实现 四、论文参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 随着互联网…

6--SpringBootWeb案例(详解)

目录 环境搭建 部门管理 查询部门 接口文档 代码 删除部门 接口文档 代码 新增部门 接口文档 代码 已有前端&#xff0c;根据接口文档完成后端功能的开发 成品如下&#xff1a; 环境搭建 1. 准备数据库表 (dept 、 emp) -- 部门管理 create table dept( id int un…

快速入门Vue

Vue是什么 Vue.js&#xff08;通常简称为Vue&#xff09;是一个开源的JavaScript框架&#xff0c;用于构建用户界面和单页应用程序&#xff08;SPA&#xff09;。它由尤雨溪&#xff08;Evan You&#xff09;在2014年开发并发布。Vue的核心库只关注视图层&#xff0c;易于上手…

python实现多个pdf文件合并

打印发票时&#xff0c;需要将pdf合并成一个&#xff0c;单页两张打印。网上一些pdf合并逐渐收费&#xff0c;这玩意儿都能收费&#xff1f;自己写一个脚本使用。 实现代码&#xff1a; 输入pdf文件夹路径data_dir&#xff0c;统计目录下的“合并后的PDF”文件夹下&#xff0c;…

从边缘设备到云端平台,合宙DTURTU打造无缝物联网解决方案

如今&#xff0c;物联网&#xff08;IoT&#xff09;技术飞速发展&#xff0c;万物互联的时代已然到来&#xff0c;那么&#xff0c;高效、稳定地连接边缘设备与云端平台&#xff0c;实现数据的实时采集、传输与处理&#xff0c;就成为了推动物联网应用落地的关键。 DTU&#…

# wps必须要登录激活才能使用吗?

WPS 必须登录激活才能使用吗&#xff1f; 如下图&#xff0c;当我们使用WPS时&#xff0c;不登录会显示工具栏灰色不可用状态。 答&#xff1a;WPS 不一定要登录激活才能使用。 一、免费使用的情况 1、基础功能 在不登录的情况下&#xff0c;用户可以使用 WPS 的一些基础功…

【delphi】正则判断windows完整合法文件名,包括路径

在 Delphi 中&#xff0c;可以使用正则表达式来检查 Windows 文件名称或路径是否合法。合法的文件名和路径要求符合以下几点&#xff1a; 禁止的字符&#xff1a;文件名和路径不能包含以下字符&#xff1a;<, >, :, ", /, \, |, ?, *。文件名不能以空格或点结束。…