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

news/2024/9/19 8:18:00/ 标签: c++, 开发语言

文章目录

  • 前言
  • 一、函数重载
    • 概念与使用
    • 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.ppmy.cn/news/1527079.html

相关文章

Java网络编程 TCP通信(Socket 与 ServerSocket)

1.TCP通信原理 TCP通信涉及两个端点&#xff1a;客户端和服务器。服务器端使用 ServerSocket 监听特定端口&#xff0c;等待客户端的连接请求。客户端使用 Socket 连接到服务器的IP地址和端口。一旦连接建立&#xff0c;双方就可以通过输入输出流进行数据交换. ServerSocket是…

详细分析Uniapp中的轮播图基本知识(附Demo)

目录 前言1. 基本知识2. Demo2.1 基本2.2 自定义分页2.3 自定义动画 3. 扩展 前言 先看代码示例&#xff1a; 实现了一个带有分页指示器的轮播图组件 <template><view class"work-container"><!-- 轮播图 --><uni-swiper-dot class"uni…

【电脑组装】✈️从配置拼装到安装系统组装自己的台式电脑

目录 &#x1f378;前言 &#x1f37b;一、台式电脑基本组成 &#x1f37a;二、组装 &#x1f379;三、安装系统 &#x1f44b;四、系统设置 &#x1f440;五、章末 &#x1f378;前言 小伙伴们大家好&#xff0c;上篇文章分享了在平时开发的时候遇到的一种项目整合情况&…

TCP/IP网络编程概念及Java实现TCP/IP通讯Demo

背景 在当今数字化的世界中&#xff0c;网络通信是连接各种设备和系统的关键。TCP/IP协议作为互联网通信的基石&#xff0c;被广泛应用于各种网络场景。了解TCP/IP网络编程的概念&#xff0c;并掌握如何在Java中实现TCP/IP通讯&#xff0c;对于开发人员来说是非常重要的。 TC…

webpack打包原理

目录 1、搭建结构&#xff0c;读取配置参数2、配置参数对象初始化 Compiler&#xff08;new Compiler(webpackOptions)&#xff09;3、挂载配置文件中的插件&#xff0c;4、执行Compiler 中的 run 方法进行编译5、根据配置文件中的entry 配置项找到所有的入口6、从入口文件出发…

JavaScript 基础 - 第16天_AJAX入门

文章目录 Day01_Ajax入门目录学习目标01.AJAX 概念和 axios 使用目标讲解小结 02.认识 URL目标讲解小结 03.URL 查询参数目标讲解小结 04.案例-查询-地区列表目标讲解小结 05.常用请求方法和数据提交目标讲解小结 06.axios 错误处理目标讲解小结 07.HTTP 协议-请求报文目标讲解…

椋鸟C++笔记#7:标准模板库STL初识

文章目录 标准模板库&#xff08;Standard Template Library&#xff09;STL的版本P.J.版RW版SGI版 STL的组成部分 萌新的学习笔记&#xff0c;写错了恳请斧正。 标准模板库&#xff08;Standard Template Library&#xff09; 标准模板库STL&#xff0c;是C标准库的一个非常重…

Snowflake 如何通过 Apache Iceberg 和 Polaris 为大数据的未来提供动力

Snowflake 的使命是让每个组织都成为数据驱动型组织。凭借围绕 Apache Iceberg 的最新创新和 Polaris 的推出,这家数据云公司使开发人员、工程师和架构师能够比以往任何时候都更快、更轻松地利用大数据获得变革性的业务见解。 将开放标准引入数据云 Snowflake 战略的核心是采…

教师薪酬管理系统的设计与实现

摘 要 传统信息的管理大部分依赖于管理人员的手工登记与管理&#xff0c;然而&#xff0c;随着近些年信息技术的迅猛发展&#xff0c;让许多比较老套的信息管理模式进行了更新迭代&#xff0c;老师信息因为其管理内容繁杂&#xff0c;管理数量繁多导致手工进行处理不能满足广…

HarmonyOS4升级到Harmonyos Next(Api 11)系列教程

目前此教程基于HarmonyOS NEXT Developer Preview、OpenHarmony&#xff08;Api 11&#xff09;讲解&#xff0c;兼容Api 12&#xff0c;HarmonyOS 5&#xff08;HarmonyOS Next&#xff09;正式发布后新增的Api还会在第一时间更新。 HarmonyOS NEXT和HarmonyOS 4的基本语法都是…

文件批量添加水印和密码合并单元格完整版

这段代码是一个 Java 方法&#xff0c;用于向文件添加水印和密码。您解释一下&#xff1a; 首先&#xff0c;它接受一个 fileAddress 参数&#xff0c;表示文件的地址。 然后&#xff0c;它创建了一个线程安全的列表 fileDatas&#xff0c;用于存储文件数据。 接下来&#xff…

ZW3D二次开发_UI_非模板表单_设置表单显示位置

1.ZW3D弹出非模板表单时可以设置弹出位置&#xff08;居中、左下角、右上角等&#xff09; 2.假设已创建好非模板表单 3.在Form属性中添加form_pos属性 4.输入值 base,CTR,0.0 &#xff0c;如下图 也可以设置为其他值显示在不同的位置&#xff0c;如下 5.重新编译&#xff0c;…

使用Jenkins扩展钉钉消息通知

Jenkins借助钉钉插件&#xff0c;实现当构建失败时&#xff0c;自动触发钉钉预警。虽然插件允许自定义消息主体&#xff0c;支持使用 Jenkins环境变量&#xff0c;但是局限性依旧很大。当接收到钉钉通知后&#xff0c;若想进一步查看报错具体原因&#xff0c;仍完全依赖邮件通知…

docker-ce.repo源、kubernetes.repo源

一、docker-ce.repo源 [docker-ce-stable] nameDocker CE Stable - $basearch baseurlhttps://mirrors.aliyun.com/docker-ce/linux/centos/$releasever/$basearch/stable enabled1 gpgcheck1 gpgkeyhttps://mirrors.aliyun.com/docker-ce/linux/centos/gpg [docker-ce-stable-…

优购电商小程序的设计与实现+ssm(lw+演示+源码+运行)

优购电商小程序 摘 要 随着社会的发展&#xff0c;社会的方方面面都在利用信息化时代的优势。互联网的优势和普及使得各种系统的开发成为必需。 本文以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&#xff0c;它主要是采用java语言技术和mysql数据库来完成对…

VMware网络配置

在 VMware 中,网络配置至关重要,它决定了虚拟机与物理网络以及其他虚拟机之间的通信方式。VMware 提供了多种网络连接方式,适用于不同的应用场景。以下是 VMware 网络配置的常见类型及其配置方法: 1. VMware 网络类型 VMware 主要提供以下三种网络连接类型: 1.1 桥接网…

【系统架构设计师-2012年真题】案例分析-答案及详解

更多内容请见: 备考系统架构设计师-核心总结索引 文章目录 【材料1】【问题 1】(11 分)【问题 2】(8 分)【问题 3】(6 分)【材料2】【问题 1】(6 分)【问题 2】(9 分)【问题 3】(10 分)【材料3】【问题 1】(共 9 分)【问题 2】(共 16 分)【材料4】【问题 1】(共 10 分)【问题 …

SpringBoot 消息队列RabbitMQ 消息确认机制确保消息发送成功和失败 生产者确认

介绍 有Publisher Confirm(成功)和Publisher Return(失败)两种确认机制。开启确机制认后&#xff0c;在MQ成功收到消息后会返回消息给生产者。 消息投递到了MQ &#xff0c;但是路由失败。此时会通过PublisherReturn返回路由异常原因&#xff0c;然后返回ACK&#xff0c;告知…

C++二叉搜索树学习

目录 一、二叉搜索树概念 二、二叉搜索树的性能分析 三、二叉搜索树的构建 一、二叉搜索树概念 二叉搜索树又叫做二叉排序树&#xff0c;它可以是一颗空树&#xff0c;或者是具有以下性质的二叉树&#xff1a; 若该树的左子树不为空&#xff0c;那么左子树上的任一节点都小…

MATLAB系列04:循环结构

MATLAB系列04&#xff1a;循环结构 4. 循环结构4.1 while循环4.2 for循环4.2.1 运算的细节4.2.2 break语句和continue语句4.2.3 嵌套循环 4.3 逻辑数组和向量化4.3.1 逻辑数组的重要性4.3.2 用 if/else 结构和逻辑数组创建等式 4.4 总结 4. 循环结构 循环(loop)是一种 MATLAB …