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

news2025/1/4 17:29:37

文章目录

  • 前言
  • 一、函数重载
    • 概念与使用
    • 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.c
	int sum(int num1, int num2)
	{
		return num1 + num2;
	}
	
	// main.c
	extern 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; // 0
	cout << "TestFunc1(struct A a):" << end2 - begin2 << endl; // 5

	return 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;// 打印结果为int
	return 0;
}
  1. 在同一行定义多个变量必须是同一类型
int main()
{
	auto a = 1, b = 2; // right
	auto 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 };// err
	return 0;
}

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

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

其实是抄的Python的作业

	int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	//将数组元素值全部乘以2
	for (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.coloradmin.cn/o/2143453.html

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

相关文章

C++ | Leetcode C++题解之第414题第三大的数

题目&#xff1a; 题解&#xff1a; class Solution { public:int thirdMax(vector<int> &nums) {int *a nullptr, *b nullptr, *c nullptr;for (int &num : nums) {if (a nullptr || num > *a) {c b;b a;a &num;} else if (*a > num &&am…

一般在写SQL时需要注意哪些问题,可以提高查询的效率?

很多人写SQL按照自己喜好&#xff0c;没有规则意识&#xff0c;这对于自主查询影响不大&#xff0c;你爱怎么搞就怎么搞&#xff0c;一旦涉及到提交任务或团队共享&#xff0c;就不能乱写了&#xff0c;会浪费资源影响到开发效率&#xff0c;严重的甚至会服务器瘫痪。 提几个关…

深度学习之图像数据集增强(Data Augmentation)

文章目录 一、 数据增强概述二、python实现传统数据增强参考文献 一、 数据增强概述 数据增强&#xff08;Data Augmentation&#xff09;是一种技术&#xff0c;通过对现有数据进行各种变换和处理来生成新的训练样本&#xff0c;从而增加数据集的多样性和数量。这些变换可以是…

dubbo三

dubbo dubbo架构各层说明 URL举例解析 消费者引用服务过程 项目初始化

世界排名第一的数码照片和图形放大软件PhotoZoom Pro 9

BenVista PhotoZoom Pro 9 是世界排名第一的数码照片和图形放大和缩小软件解决方案。 PhotoZoom Pro 9 配备了我们全新的 S-Spline Max AI 图像调整大小技术&#xff0c;可产生比以往任何时候都更高质量的图像放大。 您所要做的就是指定您想要的图像大小&#xff0c;它实际上是…

【3D打印】使用simplify 3D切片更改Gcode手动断电续打、掉电、未打完继续打印、补救

一、问题描述 有些时候会遇到3D打印机没料但机器还在继续打、掉电重启后未正常恢复打印、挤出机端没有料但断料检测未触发等情况。我们又不想打印放弃&#xff0c;但又想继续之前的进度打印。 这时候我们需要更改3D打印文件的Gcode参数来进行继续打印。 至于什么是Gcode&…

电磁阀,线性电磁阀信号驱动隔离变送器

电磁阀,线性电磁阀信号驱动隔离变送器 定义:用模拟信号控制电磁阀门开关驱动的信号隔离产品,广泛用于流量控制,加料控制. 电磁阀开驱动隔离变送器为一进一出系列,型号是:JSD TAP-1001系列 该电磁阀驱动设备具有以下特点:特征&#xff1a; ◆低成本,PA66阻燃外壳,国际标准DIN35导…

程序设计题(41-48)

第四十一题 题目 #include <stdio.h> #include <math.h> double fun(double x , int n) {}main() { void NONO ();printf("%f\n", fun(0.3,10));NONO();getchar(); }void NONO () {/* 本函数用于打开文件&#xff0c;输入数据&#xff0c;调用函数&am…

防火墙——NAT

目录 NAT NAT分类 旧分类 新分类 NAT配置 源NAT​编辑 配置源NAT地址池​编辑 关于源NAT环路问题 环境如下​编辑 防火墙nat​编辑​编辑 路由器要配置指向11.0.0.0 网段的静态路由​编辑 测试​编辑 如果此时有外网用户直接pingNAT地址&#xff0c;则环路出现。​…

【手撕算法】快速排序(递归分治法)Python实现

一、算法 class Solution:def Partition(self, nums, low, high):pivotkey nums[low] # 元素copied, nums[low]空了出来while low < high:while low < high and nums[high] > pivotkey:high high - 1 # 直到找到一个nums[high]<pivotkey位置nums[low] nums[h…

【C语言】联合体枚举的讲解

目录 ✨声明&#xff01;&#xff01;&#xff01;&#xff1a; 联合体与结构体只有一个区别&#xff0c;那就是内存存储方式不同 &#x1f495;1.联合体的声明 &#x1f495;2.联合体内存的存储 &#x1f495;3.联合体字节大小的计算 例题2&#xff1a; ✨4.枚举的声明…

2024最新股票系统源码 附教程

1.环境 环境 php7.4 sql 5.7 Nginx1.2 tomcat-8 redis 放行1-65535 2.创建5个网站。xxx.com替换你的域名 ftp.xxx.com api.xxx.com agent.xxx.com admin.xxx.com wap.xxx.com api设置反向代理 代理名称 api 目标URL http://127.0.0.1:8091 新建ftp 目录指向新建的…

绝缘子缺陷检测数据集

绝缘子缺陷检测数据集&#xff0c;2800张高清照片&#xff0c;已打好标签txt格式&#xff0c;可直接进行目标检测。7类标签&#xff1a;玻璃绝缘子&#xff0c;玻璃片脏污&#xff0c;玻璃片缺损&#xff0c;聚合物片脏污&#xff0c;聚合物片缺损&#xff0c;聚合物绝缘子&…

K8S - Access Control 机制介绍

作为开发人员&#xff0c; 我们通常会直接用root 帐号操作 k8s master node 里的kubectl 命令&#xff0c;并不能感知k8s 多用户权限管理存在。 即使自动化&#xff0c; 我们也会考虑用ansible 来远程操作master node… 所以大部分开发人员默认上是不用深入研究k8s的Access c…

Qt优秀开源项目之二十三:QSimpleUpdater

QSimpleUpdater是开源的自动升级模块&#xff0c;用于检测、下载和安装更新。 github地址&#xff1a;https://github.com/alex-spataru/QSimpleUpdater QSimpleUpdater目前Star不多&#xff08;911个&#xff09;&#xff0c;但已在很多开源项目看到其身影&#xff0c;比如Not…

[数据结构]算法复杂度详解

文章目录 一、引言1、想象数据结构与算法的奇妙世界2、算法复杂度的轻松解读3、数据结构与算法的温馨寄语 二、轻松掌握复杂度基础1、时间复杂度&#xff1a;算法速度的衡量尺2、空间复杂度&#xff1a;算法占地的衡量尺3、常见的复杂度 三、复杂度的计算1、时间复杂度计算2、空…

联想键盘鼠标套装Liteon SK-8861 银丝带键盘、鼠标对码方法

知识点分析: 最近在IdeaCentre B520e、IdeaCentre A720等机型&#xff0c;标配2.4G无线键鼠套装&#xff1a;Liteon SK-8861。由三部分组成&#xff1a;USB接收器、鼠标、键盘。鼠标、键盘同时和USB接收器连接&#xff0c;未开箱时USB接收器包装在鼠标内。标配的键鼠套装不需要…

linux 内核代码学习(九)--Linux内核启动和文件系统

一个比较顺手的学习平台可以达到事半功倍的效果&#xff0c;这里使用的平台环境主要是利用了主机和从机间的文件共享&#xff0c;以及从机自带的编译环境可以比较顺利的编译busybox1.0版本&#xff0c;方便进行内核和文件系统的测试了学习。 主机环境&#xff1a;vmware7.0win1…

C语言 | Leetcode C语言题解之第413题等差数列划分

题目&#xff1a; 题解&#xff1a; int numberOfArithmeticSlices(int* nums, int numsSize) {if (numsSize 1) {return 0;}int d nums[0] - nums[1], t 0;int ans 0;// 因为等差数列的长度至少为 3&#xff0c;所以可以从 i2 开始枚举for (int i 2; i < numsSize; i…

Qt 模型视图(三):视图类QAbstractItemView

文章目录 Qt 模型视图(三):视图类QAbstractItemView1.基本概念1.1.使用现有视图1.2.使用模型1.3.使用模型的多个视图1.4.在视图之间共享选择 Qt 模型视图(三):视图类QAbstractItemView ​ 模型/视图结构是一种将数据存储和界面展示分离的编程方法。模型存储数据&#xff0c;视…